/*
* Copyright (C) 2009 - 2013 Niall 'Rivernile' Scott
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors or contributors be held liable for
* any damages arising from the use of this software.
*
* The aforementioned copyright holder(s) hereby grant you a
* non-transferrable right to use this software for any purpose (including
* commercial applications), and to modify it and redistribute it, subject to
* the following conditions:
*
* 1. This notice may not be removed or altered from any file it appears in.
*
* 2. Any modifications made to this software, except those defined in
* clause 3 of this agreement, must be released under this license, and
* the source code of any modifications must be made available on a
* publically accessible (and locateable) website, or sent to the
* original author of this software.
*
* 3. Software modifications that do not alter the functionality of the
* software but are simply adaptations to a specific environment are
* exempt from clause 2.
*/
package uk.org.rivernile.edinburghbustracker.android.fragments.general;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.util.List;
import uk.org.rivernile.edinburghbustracker.android.R;
/**
* This Fragment allows the user to manually enter a bus stop code or to
* initiate the QR code scanning.
*
* @author Niall Scott
*/
public class EnterStopCodeFragment extends Fragment
implements View.OnClickListener, View.OnKeyListener {
private static final Intent BARCODE_INTENT;
private Callbacks callbacks;
private EditText txt;
private InputMethodManager imm;
private boolean barcodePackageAvailable = false;
private Button scanButton, submitButton;
static {
// Set up this Intent statically as it can be reused.
BARCODE_INTENT = new Intent("com.google.zxing.client.android.SCAN");
BARCODE_INTENT.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
BARCODE_INTENT.putExtra("QR_CODE_MODE", true);
}
/**
* {@inheritDoc}
*/
@Override
public void onAttach(final Activity activity) {
super.onAttach(activity);
try {
callbacks = (Callbacks) activity;
} catch (ClassCastException e) {
throw new IllegalStateException(activity.getClass().getName() +
" does not implement " + Callbacks.class.getName());
}
}
/**
* {@inheritDoc}
*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get the Input Method Service.
imm = (InputMethodManager)getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE);
}
/**
* {@inheritDoc}
*/
@Override
public View onCreateView(final LayoutInflater inflater,
final ViewGroup container, final Bundle savedInstanceState) {
// Inflate the View from the XML resource.
final View v = inflater.inflate(R.layout.enterstopcode, container,
false);
// Get the UI elements.
submitButton = (Button)v.findViewById(R.id.enterstopcode_submit);
submitButton.setOnClickListener(this);
scanButton = (Button)v.findViewById(R.id.enterstopcode_barcode_button);
scanButton.setOnClickListener(this);
txt = (EditText)v.findViewById(R.id.enterstopcode_entry);
txt.setOnKeyListener(this);
txt.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(final Editable s) {
// Only enable the confirm button if the length is 8 chars.
if(s.length() == 8) {
submitButton.setEnabled(true);
} else {
submitButton.setEnabled(false);
}
}
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) { }
@Override
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) { }
});
return v;
}
/**
* {@inheritDoc}
*/
@Override
public void onResume() {
super.onResume();
// Get a list of all applications which handle the barcode intent.
final List<ResolveInfo> packages = getActivity().getPackageManager()
.queryIntentActivities(BARCODE_INTENT, 0);
// If the list does not exist or is empty, there are no Activities to
// handle the barcode intent.
if(packages == null || packages.isEmpty()) {
barcodePackageAvailable = false;
} else {
barcodePackageAvailable = true;
}
}
/**
* {@inheritDoc}
*/
@Override
public void onClick(final View v) {
if(v == scanButton) {
if(barcodePackageAvailable) {
try {
// Attempt to start the barcode scanning Activity if it is
// available.
startActivityForResult(BARCODE_INTENT, 0);
} catch(ActivityNotFoundException e) { }
} else {
// The barcode scanning Activity is not available, alert the
// user.
callbacks.onAskInstallBarcodeScanner();
}
} else if(v == submitButton) {
// The user typed in a stop code, load the bus times.
task();
}
}
/**
* {@inheritDoc}
*/
@Override
public void onActivityResult(final int requestCode, final int resultCode,
final Intent data) {
final Activity activity = getActivity();
// The result code signified success.
if(resultCode == Activity.RESULT_OK) {
// Make sure there is a data Intent. There have been some crashes
// caused by this being null.
if (data == null) {
Toast.makeText(activity, R.string.enterstopcode_scan_error,
Toast.LENGTH_LONG).show();
return;
}
// Get the data from the Intent.
final Uri uri = Uri.parse(data.getStringExtra("SCAN_RESULT"));
// We can only handle hierarchical URIs. Make sure it is so.
if(!uri.isHierarchical()) {
// Tell the user the URI was invalid.
Toast.makeText(activity, R.string.enterstopcode_invalid_qrcode,
Toast.LENGTH_LONG).show();
return;
}
// Get the busStopCode parameter from the URI.
final String stopCode = uri.getQueryParameter("busStopCode");
// Do basic sanity checking on the parameter.
if(stopCode != null && stopCode.length() > 0) {
// Set the EditText to the value of this stop code.
txt.setText(stopCode);
// Launch bus times.
callbacks.onShowBusTimes(stopCode);
} else {
Toast.makeText(activity, R.string.enterstopcode_invalid_qrcode,
Toast.LENGTH_LONG).show();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean onKey(final View v, final int keyCode,
final KeyEvent event) {
// Grab the enter key.
if(event.getAction() == KeyEvent.ACTION_UP &&
keyCode == KeyEvent.KEYCODE_ENTER &&
txt.getText().length() == 8) {
imm.hideSoftInputFromWindow(txt.getWindowToken(), 0);
task();
}
return false;
}
/**
* Call the DisplayStopDataActivity with the stop code given in the EditText
* box.
*/
private void task() {
final Activity activity = getActivity();
// Should never happen, but make sure there is a stop code to work with.
if(txt.getText().length() == 0) {
Toast.makeText(activity, R.string.enterstopcode_toast_inputerr,
Toast.LENGTH_LONG).show();
} else {
// Load bus times.
callbacks.onShowBusTimes(txt.getText().toString().trim());
}
}
/**
* Any Activities which host this Fragment must implement this interface to
* handle navigation events.
*/
public static interface Callbacks {
/**
* This is called when the user is asked if they want to install
* a barcode scanner or not.
*/
public void onAskInstallBarcodeScanner();
/**
* This is called when the user wishes to view bus stop times.
*
* @param stopCode The bus stop to view times for.
*/
public void onShowBusTimes(String stopCode);
}
}