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.CHOSEN_ANSWER_CORRECT; 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 import static com.example.android.wearable.quiz.Constants.RESET_QUIZ_PATH; 32 33 import android.app.Activity; 34 import android.graphics.Color; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.util.Log; 38 import android.view.LayoutInflater; 39 import android.view.View; 40 import android.widget.Button; 41 import android.widget.EditText; 42 import android.widget.LinearLayout; 43 import android.widget.RadioGroup; 44 import android.widget.TextView; 45 46 import com.google.android.gms.common.ConnectionResult; 47 import com.google.android.gms.common.api.GoogleApiClient; 48 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; 49 import com.google.android.gms.common.api.ResultCallback; 50 import com.google.android.gms.common.data.FreezableUtils; 51 import com.google.android.gms.wearable.DataApi; 52 import com.google.android.gms.wearable.DataEvent; 53 import com.google.android.gms.wearable.DataEventBuffer; 54 import com.google.android.gms.wearable.DataItem; 55 import com.google.android.gms.wearable.DataItemBuffer; 56 import com.google.android.gms.wearable.DataMap; 57 import com.google.android.gms.wearable.DataMapItem; 58 import com.google.android.gms.wearable.MessageApi; 59 import com.google.android.gms.wearable.MessageEvent; 60 import com.google.android.gms.wearable.Node; 61 import com.google.android.gms.wearable.NodeApi; 62 import com.google.android.gms.wearable.PutDataMapRequest; 63 import com.google.android.gms.wearable.PutDataRequest; 64 import com.google.android.gms.wearable.Wearable; 65 66 import org.json.JSONArray; 67 import org.json.JSONException; 68 import org.json.JSONObject; 69 70 import java.io.IOException; 71 import java.util.ArrayList; 72 import java.util.Collections; 73 import java.util.HashMap; 74 import java.util.List; 75 import java.util.Map; 76 import java.util.PriorityQueue; 77 78 /** 79 * Allows the user to create questions, which will be put as notifications on the watch's stream. 80 * The status of questions will be updated on the phone when the user answers them. 81 */ 82 public class MainActivity extends Activity implements DataApi.DataListener, 83 MessageApi.MessageListener, ConnectionCallbacks, 84 GoogleApiClient.OnConnectionFailedListener { 85 86 private static final String TAG = "ExampleQuizApp"; 87 private static final String QUIZ_JSON_FILE = "Quiz.json"; 88 89 // Various UI components. 90 private EditText questionEditText; 91 private EditText choiceAEditText; 92 private EditText choiceBEditText; 93 private EditText choiceCEditText; 94 private EditText choiceDEditText; 95 private RadioGroup choicesRadioGroup; 96 private TextView quizStatus; 97 private LinearLayout quizButtons; 98 private LinearLayout questionsContainer; 99 private Button readQuizFromFileButton; 100 private Button resetQuizButton; 101 102 private GoogleApiClient mGoogleApiClient; 103 private PriorityQueue<Question> mFutureQuestions; 104 private int mQuestionIndex = 0; 105 private boolean mHasQuestionBeenAsked = false; 106 107 // Data to display in end report. 108 private int mNumCorrect = 0; 109 private int mNumIncorrect = 0; 110 private int mNumSkipped = 0; 111 112 private static final Map<Integer, Integer> radioIdToIndex; 113 114 static { 115 Map<Integer, Integer> temp = new HashMap<Integer, Integer>(4); 116 temp.put(R.id.choice_a_radio, 0); 117 temp.put(R.id.choice_b_radio, 1); 118 temp.put(R.id.choice_c_radio, 2); 119 temp.put(R.id.choice_d_radio, 3); 120 radioIdToIndex = Collections.unmodifiableMap(temp); 121 } 122 123 @Override 124 protected void onCreate(Bundle savedInstanceState) { 125 super.onCreate(savedInstanceState); 126 setContentView(R.layout.main); 127 128 mGoogleApiClient = new GoogleApiClient.Builder(this) 129 .addApi(Wearable.API) 130 .addConnectionCallbacks(this) 131 .addOnConnectionFailedListener(this) 132 .build(); 133 mFutureQuestions = new PriorityQueue<Question>(10); 134 135 // Find UI components to be used later. 136 questionEditText = (EditText) findViewById(R.id.question_text); 137 choiceAEditText = (EditText) findViewById(R.id.choice_a_text); 138 choiceBEditText = (EditText) findViewById(R.id.choice_b_text); 139 choiceCEditText = (EditText) findViewById(R.id.choice_c_text); 140 choiceDEditText = (EditText) findViewById(R.id.choice_d_text); 141 choicesRadioGroup = (RadioGroup) findViewById(R.id.choices_radio_group); 142 quizStatus = (TextView) findViewById(R.id.quiz_status); 143 quizButtons = (LinearLayout) findViewById(R.id.quiz_buttons); 144 questionsContainer = (LinearLayout) findViewById(R.id.questions_container); 145 readQuizFromFileButton = (Button) findViewById(R.id.read_quiz_from_file_button); 146 resetQuizButton = (Button) findViewById(R.id.reset_quiz_button); 147 } 148 149 @Override 150 protected void onStart() { 151 super.onStart(); 152 if (!mGoogleApiClient.isConnected()) { 153 mGoogleApiClient.connect(); 154 } 155 } 156 157 @Override 158 protected void onStop() { 159 Wearable.DataApi.removeListener(mGoogleApiClient, this); 160 Wearable.MessageApi.removeListener(mGoogleApiClient, this); 161 162 // Tell the wearable to end the quiz (counting unanswered questions as skipped), and then 163 // disconnect mGoogleApiClient. 164 DataMap dataMap = new DataMap(); 165 dataMap.putInt(NUM_CORRECT, mNumCorrect); 166 dataMap.putInt(NUM_INCORRECT, mNumIncorrect); 167 if (mHasQuestionBeenAsked) { 168 mNumSkipped += 1; 169 } 170 mNumSkipped += mFutureQuestions.size(); 171 dataMap.putInt(NUM_SKIPPED, mNumSkipped); 172 if (mNumCorrect + mNumIncorrect + mNumSkipped > 0) { 173 sendMessageToWearable(QUIZ_EXITED_PATH, dataMap.toByteArray()); 174 } 175 176 clearQuizStatus(); 177 super.onStop(); 178 } 179 180 @Override 181 public void onConnected(Bundle connectionHint) { 182 Wearable.DataApi.addListener(mGoogleApiClient, this); 183 Wearable.MessageApi.addListener(mGoogleApiClient, this); 184 } 185 186 @Override 187 public void onConnectionSuspended(int cause) { 188 // Ignore 189 } 190 191 @Override 192 public void onConnectionFailed(ConnectionResult result) { 193 Log.e(TAG, "Failed to connect to Google Play Services"); 194 } 195 196 @Override 197 public void onMessageReceived(MessageEvent messageEvent) { 198 if (messageEvent.getPath().equals(RESET_QUIZ_PATH)) { 199 runOnUiThread(new Runnable() { 200 @Override 201 public void run() { 202 resetQuiz(null); 203 } 204 }); 205 } 206 } 207 208 /** 209 * Used to ensure questions with smaller indexes come before questions with larger 210 * indexes. For example, question0 should come before question1. 211 */ 212 private static class Question implements Comparable<Question> { 213 private String question; 214 private int questionIndex; 215 private String[] answers; 216 private int correctAnswerIndex; 217 218 public Question(String question, int questionIndex, String[] answers, 219 int correctAnswerIndex) { 220 this.question = question; 221 this.questionIndex = questionIndex; 222 this.answers = answers; 223 this.correctAnswerIndex = correctAnswerIndex; 224 } 225 226 public static Question fromJson(JSONObject questionObject, int questionIndex) 227 throws JSONException { 228 String question = questionObject.getString(JsonUtils.JSON_FIELD_QUESTION); 229 JSONArray answersJsonArray = questionObject.getJSONArray(JsonUtils.JSON_FIELD_ANSWERS); 230 String[] answers = new String[JsonUtils.NUM_ANSWER_CHOICES]; 231 for (int j = 0; j < answersJsonArray.length(); j++) { 232 answers[j] = answersJsonArray.getString(j); 233 } 234 int correctIndex = questionObject.getInt(JsonUtils.JSON_FIELD_CORRECT_INDEX); 235 return new Question(question, questionIndex, answers, correctIndex); 236 } 237 238 @Override 239 public int compareTo(Question that) { 240 return this.questionIndex - that.questionIndex; 241 } 242 243 public PutDataRequest toPutDataRequest() { 244 PutDataMapRequest request = PutDataMapRequest.create("/question/" + questionIndex); 245 DataMap dataMap = request.getDataMap(); 246 dataMap.putString(QUESTION, question); 247 dataMap.putInt(QUESTION_INDEX, questionIndex); 248 dataMap.putStringArray(ANSWERS, answers); 249 dataMap.putInt(CORRECT_ANSWER_INDEX, correctAnswerIndex); 250 return request.asPutDataRequest(); 251 } 252 } 253 254 /** 255 * Create a quiz, as defined in Quiz.json, when the user clicks on "Read quiz from file." 256 * @throws IOException 257 */ 258 public void readQuizFromFile(View view) throws IOException, JSONException { 259 clearQuizStatus(); 260 JSONObject jsonObject = JsonUtils.loadJsonFile(this, QUIZ_JSON_FILE); 261 JSONArray jsonArray = jsonObject.getJSONArray(JsonUtils.JSON_FIELD_QUESTIONS); 262 for (int i = 0; i < jsonArray.length(); i++) { 263 JSONObject questionObject = jsonArray.getJSONObject(i); 264 Question question = Question.fromJson(questionObject, mQuestionIndex++); 265 addQuestionDataItem(question); 266 setNewQuestionStatus(question.question); 267 } 268 } 269 270 /** 271 * Adds a question (with answer choices) when user clicks on "Add Question." 272 */ 273 public void addQuestion(View view) { 274 // Retrieve the question and answers supplied by the user. 275 String question = questionEditText.getText().toString(); 276 String[] answers = new String[4]; 277 answers[0] = choiceAEditText.getText().toString(); 278 answers[1] = choiceBEditText.getText().toString(); 279 answers[2] = choiceCEditText.getText().toString(); 280 answers[3] = choiceDEditText.getText().toString(); 281 int correctAnswerIndex = radioIdToIndex.get(choicesRadioGroup.getCheckedRadioButtonId()); 282 283 addQuestionDataItem(new Question(question, mQuestionIndex++, answers, correctAnswerIndex)); 284 setNewQuestionStatus(question); 285 286 // Clear the edit boxes to let the user input a new question. 287 questionEditText.setText(""); 288 choiceAEditText.setText(""); 289 choiceBEditText.setText(""); 290 choiceCEditText.setText(""); 291 choiceDEditText.setText(""); 292 } 293 294 /** 295 * Adds the questions (and answers) to the wearable's stream by creating a Data Item 296 * that will be received on the wearable, which will create corresponding notifications. 297 */ 298 private void addQuestionDataItem(Question question) { 299 if (!mHasQuestionBeenAsked) { 300 // Ask the question now. 301 Wearable.DataApi.putDataItem(mGoogleApiClient, question.toPutDataRequest()); 302 setHasQuestionBeenAsked(true); 303 } else { 304 // Enqueue the question to be asked in the future. 305 mFutureQuestions.add(question); 306 } 307 } 308 309 /** 310 * Sets the question's status to be the default "unanswered." This will be updated when the 311 * user chooses an answer for the question on the wearable. 312 */ 313 private void setNewQuestionStatus(String question) { 314 quizStatus.setVisibility(View.VISIBLE); 315 quizButtons.setVisibility(View.VISIBLE); 316 LayoutInflater inflater = LayoutInflater.from(this); 317 View questionStatusElem = inflater.inflate(R.layout.question_status_element, null, false); 318 ((TextView) questionStatusElem.findViewById(R.id.question)).setText(question); 319 ((TextView) questionStatusElem.findViewById(R.id.status)) 320 .setText(R.string.question_unanswered); 321 questionsContainer.addView(questionStatusElem); 322 } 323 324 @Override 325 public void onDataChanged(DataEventBuffer dataEvents) { 326 final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); 327 dataEvents.close(); 328 runOnUiThread(new Runnable() { 329 @Override 330 public void run() { 331 for (DataEvent event : events) { 332 if (event.getType() == DataEvent.TYPE_CHANGED) { 333 DataMap dataMap = DataMapItem.fromDataItem(event.getDataItem()) 334 .getDataMap(); 335 boolean questionWasAnswered = dataMap.getBoolean(QUESTION_WAS_ANSWERED); 336 boolean questionWasDeleted = dataMap.getBoolean(QUESTION_WAS_DELETED); 337 if (questionWasAnswered) { 338 // Update the answered question's status. 339 int questionIndex = dataMap.getInt(QUESTION_INDEX); 340 boolean questionCorrect = dataMap.getBoolean(CHOSEN_ANSWER_CORRECT); 341 updateQuestionStatus(questionIndex, questionCorrect); 342 askNextQuestionIfExists(); 343 } else if (questionWasDeleted) { 344 // Update the deleted question's status by marking it as left blank. 345 int questionIndex = dataMap.getInt(QUESTION_INDEX); 346 markQuestionLeftBlank(questionIndex); 347 askNextQuestionIfExists(); 348 } 349 } 350 } 351 } 352 }); 353 } 354 355 /** 356 * Updates the given question based on whether it was answered correctly or not. 357 * This involves changing the question's text color and changing the status text for it. 358 */ 359 public void updateQuestionStatus(int questionIndex, boolean questionCorrect) { 360 LinearLayout questionStatusElement = (LinearLayout) 361 questionsContainer.getChildAt(questionIndex); 362 TextView questionText = (TextView) questionStatusElement.findViewById(R.id.question); 363 TextView questionStatus = (TextView) questionStatusElement.findViewById(R.id.status); 364 if (questionCorrect) { 365 questionText.setTextColor(Color.GREEN); 366 questionStatus.setText(R.string.question_correct); 367 mNumCorrect++; 368 } else { 369 questionText.setTextColor(Color.RED); 370 questionStatus.setText(R.string.question_incorrect); 371 mNumIncorrect++; 372 } 373 } 374 375 /** 376 * Marks a question as "left blank" when its corresponding question notification is deleted. 377 */ 378 private void markQuestionLeftBlank(int index) { 379 LinearLayout questionStatusElement = (LinearLayout) questionsContainer.getChildAt(index); 380 if (questionStatusElement != null) { 381 TextView questionText = (TextView) questionStatusElement.findViewById(R.id.question); 382 TextView questionStatus = (TextView) questionStatusElement.findViewById(R.id.status); 383 if (questionStatus.getText().equals(getString(R.string.question_unanswered))) { 384 questionText.setTextColor(Color.YELLOW); 385 questionStatus.setText(R.string.question_left_blank); 386 mNumSkipped++; 387 } 388 } 389 } 390 391 /** 392 * Asks the next enqueued question if it exists, otherwise ends the quiz. 393 */ 394 private void askNextQuestionIfExists() { 395 if (mFutureQuestions.isEmpty()) { 396 // Quiz has been completed - send message to wearable to display end report. 397 DataMap dataMap = new DataMap(); 398 dataMap.putInt(NUM_CORRECT, mNumCorrect); 399 dataMap.putInt(NUM_INCORRECT, mNumIncorrect); 400 dataMap.putInt(NUM_SKIPPED, mNumSkipped); 401 sendMessageToWearable(QUIZ_ENDED_PATH, dataMap.toByteArray()); 402 setHasQuestionBeenAsked(false); 403 } else { 404 // Ask next question by putting a DataItem that will be received on the wearable. 405 Wearable.DataApi.putDataItem(mGoogleApiClient, 406 mFutureQuestions.remove().toPutDataRequest()); 407 setHasQuestionBeenAsked(true); 408 } 409 } 410 411 private void sendMessageToWearable(final String path, final byte[] data) { 412 Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback( 413 new ResultCallback<NodeApi.GetConnectedNodesResult>() { 414 @Override 415 public void onResult(NodeApi.GetConnectedNodesResult nodes) { 416 for (Node node : nodes.getNodes()) { 417 Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), path, data); 418 } 419 420 if (path.equals(QUIZ_EXITED_PATH) && mGoogleApiClient.isConnected()) { 421 mGoogleApiClient.disconnect(); 422 } 423 } 424 }); 425 } 426 427 /** 428 * Resets the current quiz when Reset Quiz is pressed. 429 */ 430 public void resetQuiz(View view) { 431 // Reset quiz status in phone layout. 432 for(int i = 0; i < questionsContainer.getChildCount(); i++) { 433 LinearLayout questionStatusElement = (LinearLayout) questionsContainer.getChildAt(i); 434 TextView questionText = (TextView) questionStatusElement.findViewById(R.id.question); 435 TextView questionStatus = (TextView) questionStatusElement.findViewById(R.id.status); 436 questionText.setTextColor(Color.WHITE); 437 questionStatus.setText(R.string.question_unanswered); 438 } 439 // Reset data items and notifications on wearable. 440 if (mGoogleApiClient.isConnected()) { 441 Wearable.DataApi.getDataItems(mGoogleApiClient) 442 .setResultCallback(new ResultCallback<DataItemBuffer>() { 443 @Override 444 public void onResult(DataItemBuffer result) { 445 if (result.getStatus().isSuccess()) { 446 List<DataItem> dataItemList = FreezableUtils.freezeIterable(result); 447 result.close(); 448 resetDataItems(dataItemList); 449 } else { 450 if (Log.isLoggable(TAG, Log.DEBUG)) { 451 Log.d(TAG, "Reset quiz: failed to get Data Items to reset"); 452 } 453 } 454 result.close(); 455 } 456 }); 457 } else { 458 Log.e(TAG, "Failed to reset data items because client is disconnected from " 459 + "Google Play Services"); 460 } 461 setHasQuestionBeenAsked(false); 462 mNumCorrect = 0; 463 mNumIncorrect = 0; 464 mNumSkipped = 0; 465 } 466 467 private void resetDataItems(List<DataItem> dataItemList) { 468 if (mGoogleApiClient.isConnected()) { 469 for (final DataItem dataItem : dataItemList) { 470 final Uri dataItemUri = dataItem.getUri(); 471 Wearable.DataApi.getDataItem(mGoogleApiClient, dataItemUri) 472 .setResultCallback(new ResetDataItemCallback()); 473 } 474 } else { 475 Log.e(TAG, "Failed to reset data items because client is disconnected from " 476 + "Google Play Services"); 477 } 478 } 479 480 /** 481 * Callback that marks a DataItem, which represents a question, as unanswered and not deleted. 482 */ 483 private class ResetDataItemCallback implements ResultCallback<DataApi.DataItemResult> { 484 @Override 485 public void onResult(DataApi.DataItemResult dataItemResult) { 486 if (dataItemResult.getStatus().isSuccess()) { 487 PutDataMapRequest request = PutDataMapRequest.createFromDataMapItem( 488 DataMapItem.fromDataItem(dataItemResult.getDataItem())); 489 DataMap dataMap = request.getDataMap(); 490 dataMap.putBoolean(QUESTION_WAS_ANSWERED, false); 491 dataMap.putBoolean(QUESTION_WAS_DELETED, false); 492 if (!mHasQuestionBeenAsked && dataMap.getInt(QUESTION_INDEX) == 0) { 493 // Ask the first question now. 494 Wearable.DataApi.putDataItem(mGoogleApiClient, request.asPutDataRequest()); 495 setHasQuestionBeenAsked(true); 496 } else { 497 // Enqueue future questions. 498 mFutureQuestions.add(new Question(dataMap.getString(QUESTION), 499 dataMap.getInt(QUESTION_INDEX), dataMap.getStringArray(ANSWERS), 500 dataMap.getInt(CORRECT_ANSWER_INDEX))); 501 } 502 } else { 503 Log.e(TAG, "Failed to reset data item " + dataItemResult.getDataItem().getUri()); 504 } 505 } 506 } 507 508 /** 509 * Clears the current quiz when user clicks on "New Quiz." 510 * On this end, this involves clearing the quiz status layout and deleting all DataItems. The 511 * wearable will then remove any outstanding question notifications upon receiving this change. 512 */ 513 public void newQuiz(View view) { 514 clearQuizStatus(); 515 if (mGoogleApiClient.isConnected()) { 516 Wearable.DataApi.getDataItems(mGoogleApiClient) 517 .setResultCallback(new ResultCallback<DataItemBuffer>() { 518 @Override 519 public void onResult(DataItemBuffer result) { 520 if (result.getStatus().isSuccess()) { 521 List<Uri> dataItemUriList = new ArrayList<Uri>(); 522 for (final DataItem dataItem : result) { 523 dataItemUriList.add(dataItem.getUri()); 524 } 525 result.close(); 526 deleteDataItems(dataItemUriList); 527 } else { 528 if (Log.isLoggable(TAG, Log.DEBUG)) { 529 Log.d(TAG, "Clear quiz: failed to get Data Items for deletion"); 530 } 531 } 532 result.close(); 533 } 534 }); 535 } else { 536 Log.e(TAG, "Failed to delete data items because client is disconnected from " 537 + "Google Play Services"); 538 } 539 } 540 541 /** 542 * Removes quiz status views (i.e. the views describing the status of each question). 543 */ 544 private void clearQuizStatus() { 545 questionsContainer.removeAllViews(); 546 quizStatus.setVisibility(View.INVISIBLE); 547 quizButtons.setVisibility(View.INVISIBLE); 548 setHasQuestionBeenAsked(false); 549 mFutureQuestions.clear(); 550 mQuestionIndex = 0; 551 mNumCorrect = 0; 552 mNumIncorrect = 0; 553 mNumSkipped = 0; 554 } 555 556 private void deleteDataItems(List<Uri> dataItemUriList) { 557 if (mGoogleApiClient.isConnected()) { 558 for (final Uri dataItemUri : dataItemUriList) { 559 Wearable.DataApi.deleteDataItems(mGoogleApiClient, dataItemUri) 560 .setResultCallback(new ResultCallback<DataApi.DeleteDataItemsResult>() { 561 @Override 562 public void onResult(DataApi.DeleteDataItemsResult deleteResult) { 563 if (Log.isLoggable(TAG, Log.DEBUG)) { 564 if (deleteResult.getStatus().isSuccess()) { 565 Log.d(TAG, "Successfully deleted data item " + dataItemUri); 566 } else { 567 Log.d(TAG, "Failed to delete data item " + dataItemUri); 568 } 569 } 570 } 571 }); 572 } 573 } else { 574 Log.e(TAG, "Failed to delete data items because client is disconnected from " 575 + "Google Play Services"); 576 } 577 } 578 579 private void setHasQuestionBeenAsked(boolean b) { 580 mHasQuestionBeenAsked = b; 581 // Only let user click on Reset or Read from file if they have answered all the questions. 582 readQuizFromFileButton.setEnabled(!mHasQuestionBeenAsked); 583 resetQuizButton.setEnabled(!mHasQuestionBeenAsked); 584 } 585 } 586