Home | History | Annotate | Download | only in 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.common.data.FreezableUtils;
     44 import com.google.android.gms.wearable.DataEvent;
     45 import com.google.android.gms.wearable.DataEventBuffer;
     46 import com.google.android.gms.wearable.DataItem;
     47 import com.google.android.gms.wearable.DataMap;
     48 import com.google.android.gms.wearable.DataMapItem;
     49 import com.google.android.gms.wearable.MessageEvent;
     50 import com.google.android.gms.wearable.Wearable;
     51 import com.google.android.gms.wearable.WearableListenerService;
     52 
     53 import java.util.Collections;
     54 import java.util.HashMap;
     55 import java.util.List;
     56 import java.util.Map;
     57 import java.util.concurrent.TimeUnit;
     58 
     59 /**
     60  * Listens to changes in DataItems, which represent quiz questions.
     61  * If a new question is created, this builds a new notification for it.
     62  * Otherwise, if a question is deleted, this cancels the corresponding notification.
     63  *
     64  * When the quiz ends, this listener receives a message telling it to create an end-of-quiz report.
     65  */
     66 public class QuizListenerService extends WearableListenerService {
     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         final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
     83         dataEvents.close();
     84 
     85         GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
     86                 .addApi(Wearable.API)
     87                 .build();
     88 
     89         ConnectionResult connectionResult = googleApiClient.blockingConnect(CONNECT_TIMEOUT_MS,
     90                 TimeUnit.MILLISECONDS);
     91         if (!connectionResult.isSuccess()) {
     92             Log.e(TAG, "QuizListenerService failed to connect to GoogleApiClient.");
     93             return;
     94         }
     95 
     96         for (DataEvent event : events) {
     97             if (event.getType() == DataEvent.TYPE_CHANGED) {
     98                 DataItem dataItem = event.getDataItem();
     99                 DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap();
    100                 if (dataMap.getBoolean(QUESTION_WAS_ANSWERED)
    101                         || dataMap.getBoolean(QUESTION_WAS_DELETED)) {
    102                     // Ignore the change in data; it is used in MainActivity to update
    103                     // the question's status (i.e. was the answer right or wrong or left blank).
    104                     continue;
    105                 }
    106                 String question = dataMap.getString(QUESTION);
    107                 int questionIndex = dataMap.getInt(QUESTION_INDEX);
    108                 int questionNum = questionIndex + 1;
    109                 String[] answers = dataMap.getStringArray(ANSWERS);
    110                 int correctAnswerIndex = dataMap.getInt(CORRECT_ANSWER_INDEX);
    111                 Intent deleteOperation = new Intent(this, DeleteQuestionService.class);
    112                 deleteOperation.setData(dataItem.getUri());
    113                 PendingIntent deleteIntent = PendingIntent.getService(this, 0,
    114                         deleteOperation, PendingIntent.FLAG_UPDATE_CURRENT);
    115                 // First page of notification contains question as Big Text.
    116                 Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle()
    117                         .setBigContentTitle(getString(R.string.question, questionNum))
    118                         .bigText(question);
    119                 Notification.Builder builder = new Notification.Builder(this)
    120                         .setStyle(bigTextStyle)
    121                         .setSmallIcon(R.drawable.ic_launcher)
    122                         .setLocalOnly(true)
    123                         .setDeleteIntent(deleteIntent);
    124 
    125                 // Add answers as actions.
    126                 Notification.WearableExtender wearableOptions = new Notification.WearableExtender();
    127                 for (int i = 0; i < answers.length; i++) {
    128                     Notification answerPage = new Notification.Builder(this)
    129                             .setContentTitle(question)
    130                             .setContentText(answers[i])
    131                             .extend(new Notification.WearableExtender()
    132                                     .setContentAction(i))
    133                             .build();
    134 
    135                     boolean correct = (i == correctAnswerIndex);
    136                     Intent updateOperation = new Intent(this, UpdateQuestionService.class);
    137                     // Give each intent a unique action.
    138                     updateOperation.setAction("question_" + questionIndex + "_answer_" + i);
    139                     updateOperation.setData(dataItem.getUri());
    140                     updateOperation.putExtra(UpdateQuestionService.EXTRA_QUESTION_INDEX,
    141                             questionIndex);
    142                     updateOperation.putExtra(UpdateQuestionService.EXTRA_QUESTION_CORRECT, correct);
    143                     PendingIntent updateIntent = PendingIntent.getService(this, 0, updateOperation,
    144                             PendingIntent.FLAG_UPDATE_CURRENT);
    145                     Notification.Action action = new Notification.Action.Builder(
    146                             questionNumToDrawableId.get(i), null, updateIntent)
    147                             .build();
    148                     wearableOptions.addAction(action).addPage(answerPage);
    149                 }
    150                 builder.extend(wearableOptions);
    151                 Notification notification = builder.build();
    152                 ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
    153                         .notify(questionIndex, notification);
    154             } else if (event.getType() == DataEvent.TYPE_DELETED) {
    155                 Uri uri = event.getDataItem().getUri();
    156                 // URI's are of the form "/question/0", "/question/1" etc.
    157                 // We use the question index as the notification id.
    158                 int notificationId = Integer.parseInt(uri.getLastPathSegment());
    159                 ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
    160                         .cancel(notificationId);
    161             }
    162             // Delete the quiz report, if it exists.
    163             ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
    164                     .cancel(QUIZ_REPORT_NOTIF_ID);
    165         }
    166         googleApiClient.disconnect();
    167     }
    168 
    169     @Override
    170     public void onMessageReceived(MessageEvent messageEvent) {
    171         String path = messageEvent.getPath();
    172         if (path.equals(QUIZ_EXITED_PATH)) {
    173             // Remove any lingering question notifications.
    174             ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancelAll();
    175         }
    176         if (path.equals(QUIZ_ENDED_PATH) || path.equals(QUIZ_EXITED_PATH)) {
    177             // Quiz ended - display overall results.
    178             DataMap dataMap = DataMap.fromByteArray(messageEvent.getData());
    179             int numCorrect = dataMap.getInt(NUM_CORRECT);
    180             int numIncorrect = dataMap.getInt(NUM_INCORRECT);
    181             int numSkipped = dataMap.getInt(NUM_SKIPPED);
    182 
    183             Notification.Builder builder = new Notification.Builder(this)
    184                     .setContentTitle(getString(R.string.quiz_report))
    185                     .setSmallIcon(R.drawable.ic_launcher)
    186                     .setLocalOnly(true);
    187             SpannableStringBuilder quizReportText = new SpannableStringBuilder();
    188             appendColored(quizReportText, String.valueOf(numCorrect), R.color.dark_green);
    189             quizReportText.append(" " + getString(R.string.correct) + "\n");
    190             appendColored(quizReportText, String.valueOf(numIncorrect), R.color.dark_red);
    191             quizReportText.append(" " + getString(R.string.incorrect) + "\n");
    192             appendColored(quizReportText, String.valueOf(numSkipped), R.color.dark_yellow);
    193             quizReportText.append(" " + getString(R.string.skipped) + "\n");
    194 
    195             builder.setContentText(quizReportText);
    196             if (!path.equals(QUIZ_EXITED_PATH)) {
    197                 // Don't add reset option if user exited quiz (there might not be a quiz to reset!).
    198                 builder.addAction(R.drawable.ic_launcher,
    199                         getString(R.string.reset_quiz), getResetQuizPendingIntent());
    200             }
    201             ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
    202                     .notify(QUIZ_REPORT_NOTIF_ID, builder.build());
    203         }
    204     }
    205 
    206     private void appendColored(SpannableStringBuilder builder, String text, int colorResId) {
    207         builder.append(text).setSpan(new ForegroundColorSpan(getResources().getColor(colorResId)),
    208                 builder.length() - text.length(), builder.length(), 0);
    209     }
    210 
    211     /**
    212      * Returns a PendingIntent that will send a message to the phone to reset the quiz when fired.
    213      */
    214     private PendingIntent getResetQuizPendingIntent() {
    215         Intent intent = new Intent(QuizReportActionService.ACTION_RESET_QUIZ)
    216                 .setClass(this, QuizReportActionService.class);
    217         return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    218     }
    219 }
    220