/* * Copyright (c) 2013 Allogy Interactive. * * 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.allogy.app.media; import android.app.*; import android.content.*; import android.graphics.Color; import android.os.Bundle; import android.os.Environment; import android.preference.PreferenceManager; import android.telephony.SmsManager; import android.text.SpannableString; import android.text.style.UnderlineSpan; import android.util.Log; import android.util.Xml; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnKeyListener; import android.view.inputmethod.InputMethodManager; import android.widget.*; import com.allogy.app.ClassroomActivity; import com.allogy.app.R; import com.allogy.app.R.string; import com.allogy.app.SettingsActivity; import com.allogy.app.provider.Academic; import com.allogy.app.provider.Academic.Progress; import com.allogy.app.xml.parsers.QuizParser; import org.xml.sax.SAXException; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; public class QuizActivity extends Activity { private static final String TAG = "QuizActivity"; private static final boolean DBG_LOG_ENABLE = true; private static final String LAST_MESSAGE = "last_message"; private static final String QUIZ_TAG = "QUIZ(id:"; public static final int MAX_SMS_LENGTH = 150; @Override protected Dialog onCreateDialog(int id, Bundle args) { switch (id) { case 0: ProgressDialog dialog = new ProgressDialog(this); dialog.setTitle("Submitting Quiz"); dialog.setMessage("...Please Wait..."); dialog.setCancelable(false); dialog.setIndeterminate(true); return dialog; case 1: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Submission Failed"); builder.setMessage("Retry or Cancel"); builder.setPositiveButton("Retry", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { submitQuiz(); } }); builder.setNegativeButton(getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissDialog(1); } }); return builder.create(); } return null; } public static final String INTENT_EXTRA_PATH = "com.allogy.app.QuizActivity.EXTRA_PATH"; public static final String INTENT_EXTRA_LESSON_ID = "com.allogy.app.QuizActivity.EXTRA_LESSON_ID"; private BroadcastReceiver sentBroadCastReceiver; private boolean QuizQuestionDisplayed = false; // Menu bar Buttons private ImageButton previousQuestion; private ImageButton nextQuestion; // Quiz Subviews private View mQuiz; private View mBegin; private View mEnd; private LinearLayout mContainer; private LinearLayout answerGroup; private LinearLayout answerReview; // Quiz Widgets private TextView questionText; private VideoView questionVideo; private TextView answerSeparator; private View separatorBar; private RadioGroup answerList; private RadioButton button; private EditText quizShortAnswer; private Button submitQuiz; private Quiz quiz; private String mPath; private int mLessonId; private boolean hasMultipleChoiceQuestions; private boolean hasShortAnswers; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent i = getIntent(); if (i.hasExtra(INTENT_EXTRA_PATH) && i.hasExtra(INTENT_EXTRA_LESSON_ID)) { mPath = i.getStringExtra(INTENT_EXTRA_PATH); mLessonId = i.getIntExtra(INTENT_EXTRA_LESSON_ID, -1); Log.i("QuizActivity", "Loading Quiz for Lesson: " + mLessonId); Log.i("QuizActivity", "Loading Quiz from : " + mPath); quiz = parseQuiz(this, Environment.getExternalStorageDirectory() + "/Allogy/Files/" + mPath); setContentView(R.layout.activity_quiz); // The top left and right buttons previousQuestion = (ImageButton) findViewById(R.id.quizLeftButton); nextQuestion = (ImageButton) findViewById(R.id.quizRightButton); // The frame where the quiz content is displayed mContainer = (LinearLayout) findViewById(R.id.QuizContentFrame); // The first page to display how the quiz needs to be taken mBegin = View.inflate(this, R.layout.quiz_begin, null); // The last page to display the answers selected mEnd = View.inflate(this, R.layout.quiz_end, null); answerReview = (LinearLayout) mEnd .findViewById(R.id.quizAnswerReview); submitQuiz = (Button) mEnd.findViewById(R.id.quiz_submit_button); submitQuiz.setOnClickListener(submitListener); // The quiz layout. mQuiz = View.inflate(this, R.layout.quiz_question, null); questionText = (TextView) mQuiz.findViewById(R.id.quizQuestionText); questionVideo = (VideoView) mQuiz .findViewById(R.id.quizQuestionVideo); answerGroup = (LinearLayout) mQuiz .findViewById(R.id.quizAnswerGroup); answerSeparator = (TextView) mQuiz .findViewById(R.id.quizSeparatorText); separatorBar = (View) mQuiz .findViewById(R.id.quizSeparatorBar); answerList = (RadioGroup) mQuiz .findViewById(R.id.quizPossibleAnswers); quizShortAnswer = (EditText) mQuiz .findViewById(R.id.quizShortAnswer); // Don't take new line characters, instead close the ime quizShortAnswer.setOnKeyListener(new OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if(keyCode == 66) { InputMethodManager mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); mgr.hideSoftInputFromWindow(quizShortAnswer.getWindowToken(), 0); return true; } return false; } }); // Initialize the quiz question type presence check booleans hasMultipleChoiceQuestions = false; hasShortAnswers = false; // Draws the beginning screen initially showBegin(); } else { Toast.makeText(this, "Could Not Load Quiz", Toast.LENGTH_SHORT) .show(); finish(); } } /* Shows the Begin Quiz screen */ public void showBegin() { // Shows the pre-quiz introductory screen mContainer.removeAllViews(); mContainer.addView(mBegin); QuizQuestionDisplayed = false; // removes the previous question button since it is the first screen of // the quiz previousQuestion.setVisibility(View.GONE); } /* Shows the end, quiz review screen */ public void showEnd() { mContainer.removeAllViews(); mContainer.addView(mEnd); QuizQuestionDisplayed = false; // removes the next question button since this is the final screen nextQuestion.setVisibility(View.GONE); answerReview.removeAllViews(); // Title text for the quiz review TextView reviewTitle = new TextView(this); reviewTitle.setText("You have reached the end of the quiz. Here are the answers you've selected:"); reviewTitle.setPadding(10, 5, 10, 0); reviewTitle.setTextColor(Color.BLACK); answerReview.addView(reviewTitle); // For each question in the quiz, the question number, text, and chosen // answer are displayed for (int i = 0; i < quiz.getNumQuestions(); i++) { Question que = quiz.getQuestion(i); TextView review = new TextView(this); SpannableString underlined = new SpannableString(getResources().getString(string.question) + " #" + (i + 1) + ": "); underlined.setSpan(new UnderlineSpan(), 0, underlined.length(), 0); review.setTextColor(Color.BLACK); review.setPadding(10, 30, 10, 0); review.setText(underlined); answerReview.addView(review); TextView questionText = new TextView(this); questionText.setText(getResources().getString(string.question)+ ": " + que.getQuestionText()); questionText.setTextColor(Color.BLACK); questionText.setPadding(10, 5, 10, 0); answerReview.addView(questionText); // If the question has multiple choices, then put the selected choice if((que.getType() & Question.TYPE_MULTIPLE_CHOICE) != 0) { hasMultipleChoiceQuestions = true; TextView answerText = new TextView(this); answerText.setPadding(10, 5, 10, 0); // if the user has chosen an answer, it will be shown here. If not, // a gray "unanswered" will appear. if (que.getSelected() != -1) { answerText.setText(getResources().getString(R.string.your_answer) + ": " + que.getAnswerText(que.getSelected())); answerText.setBackgroundColor(Color.WHITE); } else { answerText.setText("Unanswered"); answerText.setBackgroundColor(Color.GRAY); } answerReview.addView(answerText); } // If the question has short answer put the short answer too if((que.getType() & Question.TYPE_SHORT_ANSWER) != 0) { // Hide the soft keyboard in case the last question // was a short answer question if(i == (quiz.getNumQuestions() - 1)) { InputMethodManager mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); mgr.hideSoftInputFromWindow(quizShortAnswer.getWindowToken(), 0); } hasShortAnswers = true; TextView shortAnswer = new TextView(this); shortAnswer.setPadding(10, 5, 10, 0); // if the user has entered an answer, it will be shown here. If not, // a gray "unanswered" will appear. if (que.getShortAnswer() != null) { shortAnswer.setText(getResources().getString(R.string.your_answer) + ": " + que.getShortAnswer()); shortAnswer.setBackgroundColor(Color.WHITE); } else { shortAnswer.setText("Unanswered"); shortAnswer.setBackgroundColor(Color.GRAY); } answerReview.addView(shortAnswer); } } } /* Shows the screen for the question specified by the CurrentlyOn index */ public void showQuestion(Question question) { int type = question.getType(); if(DBG_LOG_ENABLE) { Log.i(TAG, "type : " + type + " Video : " + (type & Question.TYPE_VIDEO) + " Multiple Choice : " + (type & Question.TYPE_MULTIPLE_CHOICE) + " Short answer : " + (type & Question.TYPE_SHORT_ANSWER)); } //Remove all the views existing in the layout mContainer.removeAllViews(); mContainer.addView(mQuiz); // when on an actual question, both navigation buttons will be available previousQuestion.setVisibility(View.VISIBLE); nextQuestion.setVisibility(View.VISIBLE); QuizQuestionDisplayed = true; // gets and displays the text for the question being asked questionText.setText(question.getQuestionText()); questionText.setTextColor(Color.BLACK); questionText.setPadding(10, 10, 10, 20); // sets media content to visible if there is any if ((type & Question.TYPE_VIDEO) != 0x0) { questionVideo.setVisibility(View.VISIBLE); questionVideo.setVideoURI(question.getMediaUri()); questionVideo.setMediaController(new MediaController(this)); questionVideo.requestFocus(); } else { questionVideo.setVisibility(View.GONE); } // Check if the question has multiple choice answers if((type & Question.TYPE_MULTIPLE_CHOICE) != 0x0) { answerGroup.setVisibility(View.VISIBLE); // clear the radioGroup from the previous question. answerList.removeAllViews(); // populates the radioGroup with each of the possible answers, assigns // them // an id which is based on their // index in the possibleAnswers ArrayList (which is used to keep track // of // chosen answers) and toggles an answer // that has been previously selected (if any) for (int i = 0; i < question.length(); i++) { button = new RadioButton(this); String answerText = question.getAnswerText(i); button.setText(answerText); button.setTextColor(Color.BLACK); button.setId(i); if (i == question.getSelected()) { button.toggle(); } answerList.addView(button); } answerList.setGravity(Gravity.BOTTOM); } else { answerGroup.setVisibility(View.GONE); } // If the quiz has short answers, make that visible if((type & Question.TYPE_SHORT_ANSWER) != 0) { quizShortAnswer.setVisibility(View.VISIBLE); quizShortAnswer.setText(""); if(question.getShortAnswer() != null) { quizShortAnswer.setText(question.getShortAnswer()); } } else { quizShortAnswer.setVisibility(View.GONE); } } /* Previous Question Clicked */ public void onLeftClick(View v) { // before clearing the radioGroup, checks to see if the user made a // selection, and saves it if so if (QuizQuestionDisplayed) { if((quiz.getCurrentQuestion().getType() & Question.TYPE_SHORT_ANSWER) != 0) { quiz.getCurrentQuestion().setShortAnswer(quizShortAnswer.getText().toString()); } quiz.getCurrentQuestion().setSelected(answerList.getCheckedRadioButtonId()); answerList.clearCheck(); } if(quiz.isFirstQuestion() && QuizQuestionDisplayed) { showBegin(); return; } // If the END screen is being shown don't decrement the currently on index // else decrements currently on index to indicate to move to the previous // screen if(QuizQuestionDisplayed) { quiz.gotoPrevQuestion(); } showQuestion(quiz.getCurrentQuestion()); } /* Next Question Clicked */ public void onRightClick(View v) { // before clearing the radioGroup, checks to see if the user made a // selection, and saves it if so if (QuizQuestionDisplayed) { if((quiz.getCurrentQuestion().getType() & Question.TYPE_SHORT_ANSWER) != 0) { quiz.getCurrentQuestion().setShortAnswer(quizShortAnswer.getText().toString()); } quiz.getCurrentQuestion().setSelected(answerList.getCheckedRadioButtonId()); answerList.clearCheck(); } if (quiz.isLastQuestion() && QuizQuestionDisplayed) { Log.i(TAG, "Encountered Last Question, Showing the last page"); showEnd(); return; } // If the BEGIN screen is being shown don't increment the currently on index // else increment the currently on index to indicate to move to the next // screen if(QuizQuestionDisplayed) { quiz.gotoNextQuestion(); } showQuestion(quiz.getCurrentQuestion()); } /* * This method parses the XML file that contains all of the quiz information * and returns a quiz object that will be used to programatically construct * the UI for each question */ private Quiz parseQuiz(Context context, String path) { try { QuizParser parser = new QuizParser(); Xml.parse(new FileInputStream(path), Xml.Encoding.UTF_8, parser); return parser.getQuiz(); } catch (FileNotFoundException e) { Toast.makeText(context, "Quiz File not found", Toast.LENGTH_SHORT) .show(); finish(); } catch (IOException e) { finish(); } catch (SAXException e) { finish(); } return null; } private void submitQuiz() { showDialog(0); // The prefix of the sms can be just QUIZ:<id>, in case of single sms or QUIZ:<id>(m/N) // Where 'm' is the mth sms, in case of a N parts multipart sms. String msgPrefix = QUIZ_TAG + quiz.getQuizId() + ")"; String msg = ""; // This holds the multiple choice answers String msgMultipleChoice = "_<"; boolean firstQuestion = true; // This holds the short answers String msgShortAnswer = "_("; boolean firstShortAnswerQuestion = true; int num = quiz.getNumQuestions(); for (int i = 0; i < num; i++) { Question curr = quiz.getQuestion(i); if((curr.getType() & Question.TYPE_MULTIPLE_CHOICE) != 0) { if(!firstQuestion) { msgMultipleChoice += ","; } msgMultipleChoice += curr.getAnswerId(curr.getSelected()); firstQuestion = false; } if((curr.getType() & Question.TYPE_SHORT_ANSWER) != 0) { if(!firstShortAnswerQuestion) { msgShortAnswer += ","; } // Replace the commas by ";;" as commas are used for splitting in the gateway String shortAns = curr.getShortAnswer().trim(); // , is a special character which is used as a separator between the // question id and short answer. so encode , differently. shortAns = shortAns.replaceAll(",", ";;"); Log.i(TAG, "Final Short Answer : " + shortAns); // If the short answer entered is empty add the "NA" character. if(shortAns.equals("")) shortAns = "NA"; msgShortAnswer += curr.getId() + "," + shortAns; firstShortAnswerQuestion = false; } } // Construct the message to be sent here if(firstQuestion != true) { // There are multiple choice questions msg += msgMultipleChoice + "_>"; } if(firstShortAnswerQuestion != true) { // There are short answer questions msg += msgShortAnswer + "_)"; } Log.i("QuizActivity", msg); final String SENT = "com.allogy.quizactivity.SENT"; final String DELIVERED = "com.allogy.quizactivity.DELIVERED"; sentBroadCastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { boolean lastSms = intent.getBooleanExtra(LAST_MESSAGE, false); switch (getResultCode()) { case Activity.RESULT_OK: if(lastSms) { dismissDialog(0); // mark quiz as taken ContentValues values = new ContentValues(); values.put(Progress.CONTENT_ID, mLessonId); values.put(Progress.CONTENT_TYPE, Academic.CONTENT_TYPE_QUIZ); values.put(Progress.USER_ID, 0); values.put(Progress.PROGRESS, 100); context.getContentResolver().insert(Progress.CONTENT_URI, values); // go to the home activity /** Action Bar Home Button */ // Setup the intent Intent i = new Intent(QuizActivity.this, ClassroomActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // Start the activity with animation startActivity(i); overridePendingTransition(R.anim.home_enter, R.anim.home_exit); } break; // Generic failure error case SmsManager.RESULT_ERROR_GENERIC_FAILURE: // No service error case SmsManager.RESULT_ERROR_NO_SERVICE: // Null PDU error, no PDU provided case SmsManager.RESULT_ERROR_NULL_PDU: // Radio turned off error case SmsManager.RESULT_ERROR_RADIO_OFF: default: Log.i("SMSSender", "failed to send: " + getResultCode()); dismissDialog(0); showDialog(1); unregisterReceiver(sentBroadCastReceiver); } if(lastSms) { unregisterReceiver(sentBroadCastReceiver); } } }; // Get the gateway phone number SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); String phone = prefs.getString(SettingsActivity.PREF_GATEWAY, "null"); // Multipart sms needs to be sent ArrayList<String> parts = SmsManager.getDefault().divideMessage(msgPrefix + msg); int numParts = parts.size(); if(numParts == 1) { // Just a single sms needs to be sent // Create the intent that will be broadcasted by the system // when the sms is sent. Because only one sms is sent, initialize // the "last_message" boolean to be true. Intent QuizSmsSent = new Intent(SENT); QuizSmsSent.putExtra(LAST_MESSAGE, true); // Create the pending intent PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, QuizSmsSent, 0); PendingIntent deliveredPI = PendingIntent.getBroadcast(this, 0, new Intent(DELIVERED), 0); if (phone.compareTo("null") != 0) { registerReceiver(sentBroadCastReceiver, new IntentFilter(SENT)); SmsManager.getDefault().sendTextMessage(phone, null, msgPrefix + msg, sentPI, deliveredPI); } else { dismissDialog(0); showDialog(1); } } else { // If it comes here it means that the message has more than one part // we need to add the part number in each of the message ArrayList<String> newParts = constructSmsParts(msg, msgPrefix); numParts = newParts.size(); if(DBG_LOG_ENABLE) { for(int dbg=0; dbg < newParts.size(); dbg++) Log.i(TAG, "Part:"+dbg+";length:" +newParts.get(dbg).length()+ ";"+newParts.get(dbg).toString()); } // Create the intent that will be broadcasted by the system // when the sms is sent. For last sms intent, initialize // the "last_message" boolean to be true. Intent QuizLastSmsSent = new Intent(SENT); QuizLastSmsSent.putExtra(LAST_MESSAGE, true); Intent QuizSmsSent = new Intent(SENT); QuizSmsSent.putExtra(LAST_MESSAGE, false); // Create the pending intent PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, QuizSmsSent, 0); PendingIntent sentLastPI = PendingIntent.getBroadcast(this, 0, QuizLastSmsSent, 0); PendingIntent deliveredPI = PendingIntent.getBroadcast(this, 0, new Intent(DELIVERED), 0); ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(); ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(); for (int i = 0; i < numParts; i++) { if(i != (numParts - 1)) sentIntents.add(sentPI); else sentIntents.add(sentLastPI); deliveryIntents.add(deliveredPI); } if (phone.compareTo("null") != 0) { registerReceiver(sentBroadCastReceiver, new IntentFilter(SENT)); if(DBG_LOG_ENABLE) { String totMsg = ""; for (int j = 0; j < numParts; j++) { Log.i("sms constr parts ", "part:"+j+newParts.get(j)); Log.i("sms constr parts ", "part length:"+newParts.get(j).length()); totMsg += newParts.get(j); } // ArrayList<String> testParts = SmsManager.getDefault().divideMessage(totMsg); // for (int j = 0; j < testParts.size(); j++) { // Log.i("sms divide parts ", "part:"+j+testParts.get(j)); // Log.i("sms divide parts ", "part length:"+testParts.get(j).length()); // } } SmsManager.getDefault().sendMultipartTextMessage(phone, null, newParts, sentIntents, deliveryIntents); } else { dismissDialog(0); showDialog(1); } } } OnClickListener submitListener = new OnClickListener() { @Override public void onClick(View v) { boolean canSubmit = true; // all questions must be answered for (int i = 0; i < quiz.getNumQuestions(); i++) { Question que = quiz.getQuestion(i); // If the question is a multiple choice question then check if the // user has selected something if((que.getType() & Question.TYPE_MULTIPLE_CHOICE) != 0) { if (que.getSelected() == -1) { canSubmit = false; break; } } else if((que.getType() & Question.TYPE_SHORT_ANSWER) !=0) { // If the question is a shortanswer question, check if the answer // entered is not empty if (que.getShortAnswer().trim() == "") { canSubmit = false; break; } } } boolean gatewayAvailable = PreferenceManager .getDefaultSharedPreferences(QuizActivity.this).contains( SettingsActivity.PREF_GATEWAY); if (canSubmit) { if (gatewayAvailable) { submitQuiz(); } else { Toast.makeText(QuizActivity.this, "Enter Gateway Number", Toast.LENGTH_SHORT).show(); QuizActivity.this.startActivity(new Intent(QuizActivity.this, SettingsActivity.class)); } } else { Toast.makeText(QuizActivity.this, "Answer all Questions.", Toast.LENGTH_SHORT).show(); } } }; /* * This method constructs multiple single smses with part number and id in it */ public static ArrayList<String> constructSmsParts(String msg, String prefix) { ArrayList<String> msgParts = new ArrayList<String>(); // The partnumber and the prefix will be sent in each of the part // along with the body. // partNumPrefix is of length 5. if the prefix will be of length x, // The number of parts required will be the next higher integer of // (length of msg)/(MAX_SMS_LENGTH - (length of prefix + length of partnumprefix)) String partNumPrefix = "1/3"; // The length of the quiz body int bodyLengthPerPart = (MAX_SMS_LENGTH - (prefix.length() + partNumPrefix.length())); // The number of parts int numParts = (int) Math.ceil(((double) msg.length()) / bodyLengthPerPart); // The variables used by the 'for' loop int currentPartNum; int startIdx = 0; int endIdx = bodyLengthPerPart; for(currentPartNum = 1; currentPartNum <= numParts; currentPartNum++) { String part = msg.substring(startIdx, endIdx); String partPrefix = currentPartNum+ "/" + numParts + prefix; // Add the prefix to the body and put it in the array list msgParts.add(partPrefix + part); // Change the indices startIdx += bodyLengthPerPart; endIdx += bodyLengthPerPart; // If the end index is greater than the length then change it to be equal to the length. if(endIdx > msg.length()) endIdx = msg.length(); } return msgParts; } }