package de.geeksfactory.opacclient.frontend; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.PendingIntent; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentFilter.MalformedMimeTypeException; import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.widget.Toast; import org.json.JSONException; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import de.geeksfactory.opacclient.R; import de.geeksfactory.opacclient.barcode.BarcodeScanIntegrator; import de.geeksfactory.opacclient.objects.Account; import de.geeksfactory.opacclient.objects.LentItem; import de.geeksfactory.opacclient.reminder.Alarm; import de.geeksfactory.opacclient.reminder.ReminderBroadcastReceiver; import de.geeksfactory.opacclient.reminder.SyncAccountAlarmListener; import de.geeksfactory.opacclient.searchfields.SearchField; import de.geeksfactory.opacclient.searchfields.SearchQuery; import de.geeksfactory.opacclient.storage.AccountDataSource; import de.geeksfactory.opacclient.storage.DataIntegrityException; import de.geeksfactory.opacclient.storage.JsonSearchFieldDataSource; import de.geeksfactory.opacclient.storage.SearchFieldDataSource; public class MainActivity extends OpacActivity implements SearchFragment.Callback, StarredFragment.Callback, SearchResultDetailFragment.Callbacks { public static final String EXTRA_FRAGMENT = "fragment"; public static final String ACTION_SEARCH = "de.geeksfactory.opacclient.SEARCH"; public static final String ACTION_ACCOUNT = "de.geeksfactory.opacclient.ACCOUNT"; private String[][] techListsArray; private IntentFilter[] intentFiltersArray; private PendingIntent nfcIntent; private boolean nfc_capable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; private android.nfc.NfcAdapter mAdapter; private SharedPreferences sp; private Fragment rightFragment; private long account; /** * Reads the first four blocks of an ISO 15693 NFC tag as ASCII bytes into a string. * * @return String Tag memory as a string (bytes converted as ASCII) or <code>null</code> */ @SuppressLint("NewApi") public static String readPageToString(android.nfc.Tag tag) { if (tag == null) { return null; } byte[] id = tag.getId(); android.nfc.tech.NfcV tech = android.nfc.tech.NfcV.get(tag); byte[] readCmd = new byte[3 + id.length]; readCmd[0] = 0x20; // set "address" flag (only send command to this tag) readCmd[1] = 0x20; // ISO 15693 Single Block Read command byte System.arraycopy(id, 0, readCmd, 2, id.length); // copy ID StringBuilder stringbuilder = new StringBuilder(); try { tech.connect(); for (int i = 0; i < 4; i++) { readCmd[2 + id.length] = (byte) i; // 1 byte payload: block address byte[] data; data = tech.transceive(readCmd); for (byte aData1 : data) { if (aData1 > 32 && aData1 < 127) // We only want printable characters, there // might be some nullbytes in it otherwise. { stringbuilder.append((char) aData1); } } } tech.close(); } catch (IOException e) { try { tech.close(); } catch (IOException e1) { } return null; } return stringbuilder.toString().trim(); } @SuppressLint("NewApi") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); sp = PreferenceManager.getDefaultSharedPreferences(this); String itemToSelect = null; if (getIntent() != null && getIntent().getAction() != null) { if (getIntent().getAction().equals("android.intent.action.VIEW")) { urlintent(); } else if (getIntent().getAction().equals(ACTION_SEARCH) || getIntent().getAction().equals(ACTION_ACCOUNT)) { String lib = getIntent().getStringExtra("library"); AccountDataSource adata = new AccountDataSource(this); List<Account> accounts = adata.getAllAccounts(lib); if (accounts.size() == 0) { // Check if library exists (an IOException should be thrown otherwise, correct?) try { app.getLibrary(lib); } catch (IOException | JSONException e) { return; } Account account = new Account(); account.setLibrary(lib); account.setLabel(getString(R.string.default_account_name)); account.setName(""); account.setPassword(""); long id = adata.addAccount(account); selectaccount(id); } else if (accounts.size() == 1) { selectaccount(accounts.get(0).getId()); } else if (accounts.size() > 0) { if (getIntent().getAction().equals(ACTION_ACCOUNT)) { List<Account> accountsWithPassword = new ArrayList<>(); for (Account account : accounts) { if (account.getName() != null && account.getPassword() != null && !account.getName().isEmpty() && !account.getPassword().isEmpty()) { accountsWithPassword.add(account); } } if (accountsWithPassword.size() == 1) { selectaccount(accountsWithPassword.get(0).getId()); } else { showAccountSelectDialog(accounts); } } else { showAccountSelectDialog(accounts); } } if (getIntent().getAction().equals(ACTION_SEARCH)) { itemToSelect = "search"; } else { itemToSelect = "account"; } } } if (savedInstanceState == null) { if (getIntent().hasExtra(EXTRA_FRAGMENT)) { selectItem(getIntent().getStringExtra(EXTRA_FRAGMENT)); } else if (getIntent().hasExtra(ReminderBroadcastReceiver.EXTRA_ALARM_ID)) { AccountDataSource adata = new AccountDataSource(this); long alid = getIntent().getLongExtra(ReminderBroadcastReceiver.EXTRA_ALARM_ID, -1); Alarm alarm = adata.getAlarm(alid); if (alarm == null) { throw new DataIntegrityException("Unknown alarm ID " + alid + " received."); } List<LentItem> items = adata.getLentItems(alarm.media); if (items.size() > 0) { long firstAccount = items.get(0).getAccount(); boolean multipleAccounts = false; for (LentItem item : items) { if (item.getAccount() != firstAccount) { multipleAccounts = true; break; } } if (multipleAccounts) { new Handler().postDelayed(new Runnable() { @Override public void run() { drawerLayout.openDrawer(drawer); } }, 500); } else { selectaccount(firstAccount); selectItem("account"); } } } else if (itemToSelect != null) { selectItem(itemToSelect); } else if (sp.contains("startup_fragment")) { selectItem(sp.getString("startup_fragment", "search")); } else { selectItem(0); } } try { if (nfc_capable) { if (!getPackageManager().hasSystemFeature("android.hardware.nfc")) { nfc_capable = false; } } if (nfc_capable) { mAdapter = android.nfc.NfcAdapter.getDefaultAdapter(this); nfcIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); IntentFilter ndef = new IntentFilter(android.nfc.NfcAdapter.ACTION_TECH_DISCOVERED); try { ndef.addDataType("*/*"); } catch (MalformedMimeTypeException e) { throw new RuntimeException("fail", e); } intentFiltersArray = new IntentFilter[]{ndef,}; techListsArray = new String[][]{ new String[]{android.nfc.tech.NfcV.class.getName()}}; } } catch (SecurityException e) { e.printStackTrace(); } if (app.getLibrary() != null) { getSupportActionBar().setSubtitle(app.getLibrary().getDisplayName()); } showUpdateInfoDialog(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setExitTransition(null); } } private void showAccountSelectDialog(final List<Account> accounts) { new AlertDialog.Builder(this) .setTitle(R.string.account_select) .setAdapter( new AccountListAdapter(this, accounts) .setHighlightActiveAccount(false), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { selectaccount(accounts.get(which).getId()); } }).create().show(); } @Override public void accountSelected(Account account) { super.accountSelected(account); this.account = account.getId(); getSupportActionBar().setSubtitle(app.getLibrary().getDisplayName()); if (fragment instanceof OpacActivity.AccountSelectedListener) { ((OpacActivity.AccountSelectedListener) fragment).accountSelected(account); } nfcHint(); // try { // List<SearchField> fields = app.getApi() // .getSearchFields(new SQLMetaDataSource(app), app.getLibrary()); // if (fields.contains(OpacApi.KEY_SEARCH_QUERY_BARCODE)) //TODO: This won't work // with // the new implementation. But what is it for? // nfc_capable = false; // Shouldn't this // be set // to true if the library supports searching for barcodes? // } catch (OpacErrorException e) { // e.printStackTrace(); // } } public void urlintent() { Uri d = getIntent().getData(); if (d.getHost().equals("opacapp.de")) { String[] split = d.getPath().split(":"); String bib; try { bib = URLDecoder.decode(split[1], "UTF-8"); } catch (UnsupportedEncodingException e) { throw new AssertionError("UTF-8 is unknown"); } if (app.getLibrary() == null || !app.getLibrary().getIdent().equals(bib)) { AccountDataSource adata = new AccountDataSource(this); List<Account> accounts = adata.getAllAccounts(bib); if (accounts.size() > 0) { app.setAccount(accounts.get(0).getId()); } else { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("https://de.opacapp.net/" + d.getPath())); startActivity(i); return; } } String medianr = split[2]; if (medianr.length() > 1) { Intent intent = new Intent(MainActivity.this, SearchResultDetailActivity.class); intent.putExtra(SearchResultDetailFragment.ARG_ITEM_ID, medianr); startActivity(intent); } else { String title; try { title = URLDecoder.decode(split[3], "UTF-8"); } catch (UnsupportedEncodingException e) { throw new AssertionError("UTF-8 is unknown"); } Bundle query = new Bundle(); query.putString("title", title); // TODO: This won't work with most modern APIs Intent intent = new Intent(MainActivity.this, SearchResultListActivity.class); intent.putExtra("query", query); startActivity(intent); } finish(); } } @Override protected int getContentView() { return R.layout.activity_main; } @Override public void onActivityResult(int requestCode, int resultCode, Intent idata) { super.onActivityResult(requestCode, resultCode, idata); fragment.onActivityResult(requestCode, resultCode, idata); //TODO: Rewrite this for the new SearchField implementation // Barcode BarcodeScanIntegrator.ScanResult scanResult = BarcodeScanIntegrator .parseActivityResult(requestCode, resultCode, idata); if (resultCode != RESULT_CANCELED && scanResult != null) { if (scanResult.getContents() == null) { return; } if (scanResult.getContents().length() < 3) { return; } // We won't try to determine which type of barcode was // scanned anymore because of the new SearchField // implementation if (fragment instanceof SearchFragment) { // the fragment should be a Search Fragment, but check here just in case ((SearchFragment) fragment).barcodeScanned(scanResult); } } } @Override public void scanBarcode() { BarcodeScanIntegrator integrator = new BarcodeScanIntegrator(this); integrator.initiateScan(); } @SuppressLint("NewApi") @Override public void onPause() { super.onPause(); if (nfc_capable && sp.getBoolean("nfc_search", false)) { try { mAdapter.disableForegroundDispatch(this); } catch (IllegalStateException | SecurityException e) { e.printStackTrace(); } } } @SuppressLint("NewApi") @Override public void onResume() { super.onResume(); if (app.getAccount().getId() != account) { accountSelected(app.getAccount()); } nfcHint(); if (nfc_capable && sp.getBoolean("nfc_search", false)) { try { mAdapter.enableForegroundDispatch(this, nfcIntent, intentFiltersArray, techListsArray); } catch (SecurityException e) { e.printStackTrace(); } } } @TargetApi(10) private void nfcHint() { if (nfc_capable && !sp.getBoolean("nfc_search", false) && Build.VERSION.SDK_INT >= 10 && !sp.getBoolean("nfc_hint_shown", false) && app.getLibrary().isNfcSupported()) { new AlertDialog.Builder(this) .setView(LayoutInflater.from(this).inflate(R.layout.dialog_nfc_hint, null)) .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { sp.edit().putBoolean("nfc_search", true) .putBoolean("nfc_hint_shown", true).apply(); Toast.makeText(MainActivity.this, R.string.nfc_activated, Toast.LENGTH_LONG).show(); mAdapter.enableForegroundDispatch(MainActivity.this, nfcIntent, intentFiltersArray, techListsArray); } }).setNegativeButton(R.string.no_thanks, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { sp.edit().putBoolean("nfc_hint_shown", true).apply(); Toast.makeText(MainActivity.this, R.string.nfc_not_activated, Toast.LENGTH_LONG) .show(); } }).show(); } } @SuppressLint("NewApi") @Override public void onNewIntent(Intent intent) { if (nfc_capable && sp.getBoolean("nfc_search", false)) { android.nfc.Tag tag = intent.getParcelableExtra(android.nfc.NfcAdapter.EXTRA_TAG); String scanResult = readPageToString(tag); if (scanResult != null) { if (scanResult.length() > 5) { SearchFieldDataSource source = new JsonSearchFieldDataSource(this); if (source.hasSearchFields(app.getLibrary().getIdent())) { List<SearchField> fields = source .getSearchFields(app.getLibrary().getIdent()); for (SearchField field : fields) { if (field.getMeaning() == SearchField.Meaning.BARCODE) { List<SearchQuery> queries = new ArrayList<>(); queries.add(new SearchQuery(field, scanResult)); app.startSearch(this, queries); return; } } } Intent detailIntent = new Intent(this, SearchResultDetailActivity.class); detailIntent.putExtra(SearchResultDetailFragment.ARG_ITEM_ID, scanResult); startActivity(detailIntent); } } } } @Override public void showDetail(String mNr) { if (isTablet()) { rightFragment = new SearchResultDetailFragment(); Bundle args = new Bundle(); args.putString(SearchResultDetailFragment.ARG_ITEM_ID, mNr); rightFragment.setArguments(args); // Insert the fragment FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction().replace(R.id.content_frame_right, rightFragment) .commit(); } else { Intent intent = new Intent(this, SearchResultDetailActivity.class); intent.putExtra(SearchResultDetailFragment.ARG_ITEM_ID, mNr); startActivity(intent); } } @Override public void removeFragment() { if (rightFragment != null) { getSupportFragmentManager().beginTransaction().remove(rightFragment).commit(); } } @Override protected void setTwoPane(boolean active) { super.setTwoPane(active); if (!active && rightFragment != null) { try { removeFragment(); } catch (Exception e) { } } } public void showUpdateInfoDialog() { if (!getApplicationContext().getPackageName().startsWith("de.geeksfactory.opacclient")) { return; // Never show e.g. in plus edition } SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); Calendar cal = Calendar.getInstance(); cal.set(2016, 9, 15, 0, 0, 0); if ((new Date()).after(cal.getTime())) { return; } if (!sp.getBoolean(SyncAccountAlarmListener.PREF_SYNC_SERVICE, false)) { return; } if (!sp.contains("seen_update_dialog_4.5.10")) { DialogFragment newFragment = new UpdateInfoDialogFragment(); newFragment.show(getSupportFragmentManager(), "updateinfo"); sp.edit().putBoolean("seen_update_dialog_4.5.10", true).commit(); } } }