From 59217ec9baa12b8a0dc26f8ff40877226ead167e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 14 Jan 2014 21:19:43 +0100 Subject: [PATCH] fix some small layout bugs, add barcode scanner lib as source lib, fix qr code scanning issues --- OpenPGP-Keychain/build.gradle | 3 +- ...escanner-android-integration-supportv4.jar | Bin 8878 -> 0 bytes OpenPGP-Keychain/project.properties | 1 + .../res/layout/edit_key_activity.xml | 2 +- .../res/layout/edit_key_key_item.xml | 2 +- .../layout/import_keys_qr_code_fragment.xml | 12 +- OpenPGP-Keychain/res/values/strings.xml | 11 +- .../keychain/ui/ImportKeysActivity.java | 15 +- .../keychain/ui/ImportKeysQrCodeFragment.java | 42 +- .../ui/widget/ExpandableListFragment.java | 530 ------------------ .../keychain/ui/widget/SectionView.java | 2 +- README.md | 20 +- .../zxing-android-integration/.gitignore | 30 + .../AndroidManifest.xml | 11 + .../zxing-android-integration/build.gradle | 14 + .../project.properties | 15 + .../integration/android/IntentIntegrator.java | 434 ++++++++++++++ .../integration/android/IntentResult.java | 95 ++++ libraries/zxing/.gitignore | 30 + settings.gradle | 3 +- 20 files changed, 703 insertions(+), 569 deletions(-) delete mode 100644 OpenPGP-Keychain/libs/barcodescanner-android-integration-supportv4.jar delete mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ExpandableListFragment.java create mode 100644 libraries/zxing-android-integration/.gitignore create mode 100644 libraries/zxing-android-integration/AndroidManifest.xml create mode 100644 libraries/zxing-android-integration/build.gradle create mode 100644 libraries/zxing-android-integration/project.properties create mode 100644 libraries/zxing-android-integration/src/com/google/zxing/integration/android/IntentIntegrator.java create mode 100644 libraries/zxing-android-integration/src/com/google/zxing/integration/android/IntentResult.java create mode 100644 libraries/zxing/.gitignore diff --git a/OpenPGP-Keychain/build.gradle b/OpenPGP-Keychain/build.gradle index 0fffd1fd3..566559a00 100644 --- a/OpenPGP-Keychain/build.gradle +++ b/OpenPGP-Keychain/build.gradle @@ -24,8 +24,9 @@ dependencies { compile project(':libraries:ActionBarSherlock') compile project(':libraries:HtmlTextView') compile project(':libraries:StickyListHeaders:library') - compile project(':libraries:zxing') compile project(':libraries:AndroidBootstrap') + compile project(':libraries:zxing') + compile project(':libraries:zxing-android-integration') } android { diff --git a/OpenPGP-Keychain/libs/barcodescanner-android-integration-supportv4.jar b/OpenPGP-Keychain/libs/barcodescanner-android-integration-supportv4.jar deleted file mode 100644 index 4a7f1a39cdb8b8d10012d2ef2ce89af58714df68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8878 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 diff --git a/OpenPGP-Keychain/project.properties b/OpenPGP-Keychain/project.properties index 76caac668..96546d84f 100644 --- a/OpenPGP-Keychain/project.properties +++ b/OpenPGP-Keychain/project.properties @@ -14,3 +14,4 @@ android.library.reference.2=../libraries/HtmlTextView android.library.reference.3=../libraries/StickyListHeaders/library android.library.reference.4=../libraries/zxing android.library.reference.5=../libraries/AndroidBootstrap +android.library.reference.6=../libraries/zxing-android-integration diff --git a/OpenPGP-Keychain/res/layout/edit_key_activity.xml b/OpenPGP-Keychain/res/layout/edit_key_activity.xml index f8597b0df..351aec512 100644 --- a/OpenPGP-Keychain/res/layout/edit_key_activity.xml +++ b/OpenPGP-Keychain/res/layout/edit_key_activity.xml @@ -45,7 +45,7 @@ diff --git a/OpenPGP-Keychain/res/layout/import_keys_qr_code_fragment.xml b/OpenPGP-Keychain/res/layout/import_keys_qr_code_fragment.xml index f3c09a41d..5229e7cf5 100644 --- a/OpenPGP-Keychain/res/layout/import_keys_qr_code_fragment.xml +++ b/OpenPGP-Keychain/res/layout/import_keys_qr_code_fragment.xml @@ -10,7 +10,7 @@ android:layout_width="match_parent" android:layout_height="60dp" android:layout_margin="10dp" - android:text="@string/menu_import_from_qr_code" + android:text="@string/import_qr_scan_button" bootstrapbutton:bb_icon_left="fa-barcode" bootstrapbutton:bb_size="default" bootstrapbutton:bb_type="default" /> @@ -18,13 +18,19 @@ + android:layout_height="wrap_content" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:visibility="gone" /> + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:progress="0" + android:visibility="gone" /> \ No newline at end of file diff --git a/OpenPGP-Keychain/res/values/strings.xml b/OpenPGP-Keychain/res/values/strings.xml index 72709965a..746a13015 100644 --- a/OpenPGP-Keychain/res/values/strings.xml +++ b/OpenPGP-Keychain/res/values/strings.xml @@ -322,9 +322,16 @@ Import selected keys Import, Sign, and upload selected keys Import from Clipboard - Missing QR Codes: %1$s + + + Missing QR Code with ID %1$s + Missing QR Codes with IDs %1$s + + + Please start with QR Code with ID 1 QR Code malformed! Please try again! QR Code scanning finished! + Scan QR Code with \'Barcode Scanner\' OpenPGP: Decrypt File @@ -354,7 +361,7 @@ Go through all QR Codes using \'Next\', and scan them one by one. - QR Code %1$d of %2$d + QR Code with ID %1$d of %2$d Share with NFC diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 7d8f4154f..00a648355 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -76,6 +76,8 @@ public class ImportKeysActivity extends DrawerActivity implements OnNavigationLi OnNavigationListener mOnNavigationListener; String[] mNavigationStrings; + Fragment mCurrentFragment; + BootstrapButton mImportButton; BootstrapButton mImportSignUploadButton; @@ -226,12 +228,12 @@ public class ImportKeysActivity extends DrawerActivity implements OnNavigationLi } private void loadFragment(Class clss, Bundle args, String tag) { - Fragment fragment = Fragment.instantiate(this, clss.getName(), args); + mCurrentFragment = Fragment.instantiate(this, clss.getName(), args); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); // Replace whatever is in the fragment container with this fragment // and give the fragment a tag name equal to the string at the position selected - ft.replace(R.id.import_navigation_fragment, fragment, tag); + ft.replace(R.id.import_navigation_fragment, mCurrentFragment, tag); // Apply changes ft.commit(); } @@ -298,6 +300,15 @@ public class ImportKeysActivity extends DrawerActivity implements OnNavigationLi // } // } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // required for qr code scanning + if (mCurrentFragment != null) { + mCurrentFragment.onActivityResult(requestCode, resultCode, data); + } + // super.onActivityResult(requestCode, resultCode, data); + } + /** * Import keys with mImportData */ diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java index f9ead3a94..bdedbec0a 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java @@ -35,7 +35,7 @@ import android.widget.TextView; import android.widget.Toast; import com.beardedhen.androidbootstrap.BootstrapButton; -import com.google.zxing.integration.android.IntentIntegratorSupportV4; +import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; public class ImportKeysQrCodeFragment extends Fragment { @@ -45,7 +45,7 @@ public class ImportKeysQrCodeFragment extends Fragment { private TextView mText; private ProgressBar mProgress; - private String[] scannedContent; + private String[] mScannedContent; /** * Creates new instance of this fragment @@ -75,7 +75,7 @@ public class ImportKeysQrCodeFragment extends Fragment { @Override public void onClick(View v) { // scan using xzing's Barcode Scanner - new IntentIntegratorSupportV4(ImportKeysQrCodeFragment.this).initiateScan(); + new IntentIntegrator(getActivity()).initiateScan(); } }); @@ -92,9 +92,9 @@ public class ImportKeysQrCodeFragment extends Fragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { - case IntentIntegratorSupportV4.REQUEST_CODE: { - IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode, - resultCode, data); + case IntentIntegrator.REQUEST_CODE: { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, + data); if (scanResult != null && scanResult.getFormatName() != null) { Log.d(Constants.TAG, scanResult.getContents()); @@ -117,36 +117,50 @@ public class ImportKeysQrCodeFragment extends Fragment { // first qr code -> setup if (counter == 0) { - scannedContent = new String[size]; + mScannedContent = new String[size]; mProgress.setMax(size); + mProgress.setVisibility(View.VISIBLE); + mText.setVisibility(View.VISIBLE); + } + + if (mScannedContent == null || counter > mScannedContent.length) { + Toast.makeText(getActivity(), R.string.import_qr_code_start_with_one, + Toast.LENGTH_LONG).show(); + return; } // save scanned content - scannedContent[counter] = content; + mScannedContent[counter] = content; // get missing numbers ArrayList missing = new ArrayList(); - for (int i = 0; i < scannedContent.length; i++) { - if (scannedContent[i] == null) { + for (int i = 0; i < mScannedContent.length; i++) { + if (mScannedContent[i] == null) { missing.add(i); } } // update progress and text - mProgress.setProgress(scannedContent.length - missing.size()); + int alreadyScanned = mScannedContent.length - missing.size(); + mProgress.setProgress(alreadyScanned); + String missingString = ""; for (int m : missing) { - if (!missingString.equals("")) + if (!missingString.equals("")) { missingString += ", "; + } missingString += String.valueOf(m + 1); } - mText.setText(getString(R.string.import_qr_code_missing, missingString)); + + String missingText = getResources().getQuantityString( + R.plurals.import_qr_code_missing, missing.size(), missingString); + mText.setText(missingText); // finished! if (missing.size() == 0) { mText.setText(R.string.import_qr_code_finished); String result = ""; - for (String in : scannedContent) { + for (String in : mScannedContent) { result += in; } mImportActivity.loadCallback(result.getBytes(), null); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ExpandableListFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ExpandableListFragment.java deleted file mode 100644 index 54022342f..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ExpandableListFragment.java +++ /dev/null @@ -1,530 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * 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 org.sufficientlysecure.keychain.ui.widget; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.Fragment; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnCreateContextMenuListener; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.AdapterView; -import android.widget.ExpandableListAdapter; -import android.widget.ExpandableListView; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListAdapter; -import android.widget.ProgressBar; -import android.widget.TextView; - -/** - * @author Khoa Tran - * - * @see android.support.v4.app.ListFragment - * @see android.app.ExpandableListActivity - * - * ExpandableListFragment for Android < 3.0 - * - * from - * http://stackoverflow.com/questions/6051050/expandablelistfragment-with-loadermanager-for- - * compatibility-package - * - */ -public class ExpandableListFragment extends Fragment implements OnCreateContextMenuListener, - ExpandableListView.OnChildClickListener, ExpandableListView.OnGroupCollapseListener, - ExpandableListView.OnGroupExpandListener { - - static final int INTERNAL_EMPTY_ID = 0x00ff0001; - static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002; - static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003; - - final private Handler mHandler = new Handler(); - - final private Runnable mRequestFocus = new Runnable() { - public void run() { - mExpandableList.focusableViewAvailable(mExpandableList); - } - }; - - final private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { - onListItemClick((ExpandableListView) parent, v, position, id); - } - }; - - ExpandableListAdapter mAdapter; - ExpandableListView mExpandableList; - boolean mFinishedStart = false; - View mEmptyView; - TextView mStandardEmptyView; - View mProgressContainer; - View mExpandableListContainer; - CharSequence mEmptyText; - boolean mExpandableListShown; - - public ExpandableListFragment() { - } - - /** - * Provide default implementation to return a simple list view. Subclasses can override to - * replace with their own layout. If doing so, the returned view hierarchy must have a - * ListView whose id is {@link android.R.id#list android.R.id.list} and can optionally have a - * sibling view id {@link android.R.id#empty android.R.id.empty} that is to be shown when the - * list is empty. - * - *

- * If you are overriding this method with your own custom content, consider including the - * standard layout {@link android.R.layout#list_content} in your layout file, so that you - * continue to retain all of the standard behavior of ListFragment. In particular, this is - * currently the only way to have the built-in indeterminant progress state be shown. - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final Context context = getActivity(); - - FrameLayout root = new FrameLayout(context); - - // ------------------------------------------------------------------ - - LinearLayout pframe = new LinearLayout(context); - pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID); - pframe.setOrientation(LinearLayout.VERTICAL); - pframe.setVisibility(View.GONE); - pframe.setGravity(Gravity.CENTER); - - ProgressBar progress = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge); - pframe.addView(progress, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - - root.addView(pframe, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - - // ------------------------------------------------------------------ - - FrameLayout lframe = new FrameLayout(context); - lframe.setId(INTERNAL_LIST_CONTAINER_ID); - - TextView tv = new TextView(getActivity()); - tv.setId(INTERNAL_EMPTY_ID); - tv.setGravity(Gravity.CENTER); - lframe.addView(tv, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - - ExpandableListView lv = new ExpandableListView(getActivity()); - lv.setId(android.R.id.list); - lv.setDrawSelectorOnTop(false); - lframe.addView(lv, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - - root.addView(lframe, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - - // ------------------------------------------------------------------ - - root.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - - return root; - } - - /** - * Attach to list view once the view hierarchy has been created. - */ - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - ensureList(); - } - - /** - * Detach from list view. - */ - @Override - public void onDestroyView() { - mHandler.removeCallbacks(mRequestFocus); - mExpandableList = null; - mExpandableListShown = false; - mEmptyView = mProgressContainer = mExpandableListContainer = null; - mStandardEmptyView = null; - super.onDestroyView(); - } - - /** - * This method will be called when an item in the list is selected. Subclasses should override. - * Subclasses can call getListView().getItemAtPosition(position) if they need to access the data - * associated with the selected item. - * - * @param l - * The ListView where the click happened - * @param v - * The view that was clicked within the ListView - * @param position - * The position of the view in the list - * @param id - * The row id of the item that was clicked - */ - public void onListItemClick(ExpandableListView l, View v, int position, long id) { - } - - /** - * Provide the cursor for the list view. - */ - public void setListAdapter(ExpandableListAdapter adapter) { - boolean hadAdapter = mAdapter != null; - mAdapter = adapter; - if (mExpandableList != null) { - mExpandableList.setAdapter(adapter); - if (!mExpandableListShown && !hadAdapter) { - // The list was hidden, and previously didn't have an - // adapter. It is now time to show it. - setListShown(true, getView().getWindowToken() != null); - } - } - } - - /** - * Set the currently selected list item to the specified position with the adapter's data - * - * @param position - */ - public void setSelection(int position) { - ensureList(); - mExpandableList.setSelection(position); - } - - /** - * Get the position of the currently selected list item. - */ - public int getSelectedItemPosition() { - ensureList(); - return mExpandableList.getSelectedItemPosition(); - } - - /** - * Get the cursor row ID of the currently selected list item. - */ - public long getSelectedItemId() { - ensureList(); - return mExpandableList.getSelectedItemId(); - } - - /** - * Get the activity's list view widget. - */ - public ExpandableListView getListView() { - ensureList(); - return mExpandableList; - } - - /** - * The default content for a ListFragment has a TextView that can be shown when the list is - * empty. If you would like to have it shown, call this method to supply the text it should use. - */ - public void setEmptyText(CharSequence text) { - ensureList(); - if (mStandardEmptyView == null) { - throw new IllegalStateException("Can't be used with a custom content view"); - } - mStandardEmptyView.setText(text); - if (mEmptyText == null) { - mExpandableList.setEmptyView(mStandardEmptyView); - } - mEmptyText = text; - } - - /** - * Control whether the list is being displayed. You can make it not displayed if you are waiting - * for the initial data to show in it. During this time an indeterminant progress indicator will - * be shown instead. - * - *

- * Applications do not normally need to use this themselves. The default behavior of - * ListFragment is to start with the list not being shown, only showing it once an adapter is - * given with {@link #setListAdapter(ListAdapter)}. If the list at that point had not been - * shown, when it does get shown it will be do without the user ever seeing the hidden state. - * - * @param shown - * If true, the list view is shown; if false, the progress indicator. The initial - * value is true. - */ - public void setListShown(boolean shown) { - setListShown(shown, true); - } - - /** - * Like {@link #setListShown(boolean)}, but no animation is used when transitioning from the - * previous state. - */ - public void setListShownNoAnimation(boolean shown) { - setListShown(shown, false); - } - - /** - * Control whether the list is being displayed. You can make it not displayed if you are waiting - * for the initial data to show in it. During this time an indeterminant progress indicator will - * be shown instead. - * - * @param shown - * If true, the list view is shown; if false, the progress indicator. The initial - * value is true. - * @param animate - * If true, an animation will be used to transition to the new state. - */ - private void setListShown(boolean shown, boolean animate) { - ensureList(); - if (mProgressContainer == null) { - throw new IllegalStateException("Can't be used with a custom content view"); - } - if (mExpandableListShown == shown) { - return; - } - mExpandableListShown = shown; - if (shown) { - if (animate) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(), - android.R.anim.fade_out)); - mExpandableListContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(), - android.R.anim.fade_in)); - } else { - mProgressContainer.clearAnimation(); - mExpandableListContainer.clearAnimation(); - } - mProgressContainer.setVisibility(View.GONE); - mExpandableListContainer.setVisibility(View.VISIBLE); - } else { - if (animate) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(), - android.R.anim.fade_in)); - mExpandableListContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(), - android.R.anim.fade_out)); - } else { - mProgressContainer.clearAnimation(); - mExpandableListContainer.clearAnimation(); - } - mProgressContainer.setVisibility(View.VISIBLE); - mExpandableListContainer.setVisibility(View.GONE); - } - } - - /** - * Get the ListAdapter associated with this activity's ListView. - */ - public ExpandableListAdapter getListAdapter() { - return mAdapter; - } - - private void ensureList() { - if (mExpandableList != null) { - return; - } - View root = getView(); - if (root == null) { - throw new IllegalStateException("Content view not yet created"); - } - if (root instanceof ExpandableListView) { - mExpandableList = (ExpandableListView) root; - } else { - mStandardEmptyView = (TextView) root.findViewById(INTERNAL_EMPTY_ID); - if (mStandardEmptyView == null) { - mEmptyView = root.findViewById(android.R.id.empty); - } else { - mStandardEmptyView.setVisibility(View.GONE); - } - mProgressContainer = root.findViewById(INTERNAL_PROGRESS_CONTAINER_ID); - mExpandableListContainer = root.findViewById(INTERNAL_LIST_CONTAINER_ID); - View rawExpandableListView = root.findViewById(android.R.id.list); - if (!(rawExpandableListView instanceof ExpandableListView)) { - if (rawExpandableListView == null) { - throw new RuntimeException( - "Your content must have a ListView whose id attribute is " - + "'android.R.id.list'"); - } - throw new RuntimeException( - "Content has view with id attribute 'android.R.id.list' " - + "that is not a ListView class"); - } - mExpandableList = (ExpandableListView) rawExpandableListView; - if (mEmptyView != null) { - mExpandableList.setEmptyView(mEmptyView); - } else if (mEmptyText != null) { - mStandardEmptyView.setText(mEmptyText); - mExpandableList.setEmptyView(mStandardEmptyView); - } - } - mExpandableListShown = true; - mExpandableList.setOnItemClickListener(mOnClickListener); - if (mAdapter != null) { - ExpandableListAdapter adapter = mAdapter; - mAdapter = null; - setListAdapter(adapter); - } else { - // We are starting without an adapter, so assume we won't - // have our data right away and start with the progress indicator. - if (mProgressContainer != null) { - setListShown(false, false); - } - } - mHandler.post(mRequestFocus); - } - - /** - * Override this to populate the context menu when an item is long pressed. menuInfo will - * contain an {@link android.widget.ExpandableListView.ExpandableListContextMenuInfo} whose - * packedPosition is a packed position that should be used with - * {@link ExpandableListView#getPackedPositionType(long)} and the other similar methods. - *

- * {@inheritDoc} - */ - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - } - - /** - * Override this for receiving callbacks when a child has been clicked. - *

- * {@inheritDoc} - */ - public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, - int childPosition, long id) { - return false; - } - - /** - * Override this for receiving callbacks when a group has been collapsed. - */ - public void onGroupCollapse(int groupPosition) { - } - - /** - * Override this for receiving callbacks when a group has been expanded. - */ - public void onGroupExpand(int groupPosition) { - } - - // /** - // * Ensures the expandable list view has been created before Activity restores all - // * of the view states. - // * - // *@see Activity#onRestoreInstanceState(Bundle) - // */ - // @Override - // protected void onRestoreInstanceState(Bundle state) { - // ensureList(); - // super.onRestoreInstanceState(state); - // } - - /** - * Updates the screen state (current list and other views) when the content changes. - * - * @see Activity#onContentChanged() - */ - - public void onContentChanged() { - // super.onContentChanged(); - View emptyView = getView().findViewById(android.R.id.empty); - mExpandableList = (ExpandableListView) getView().findViewById(android.R.id.list); - if (mExpandableList == null) { - throw new RuntimeException( - "Your content must have a ExpandableListView whose id attribute is " - + "'android.R.id.list'"); - } - if (emptyView != null) { - mExpandableList.setEmptyView(emptyView); - } - mExpandableList.setOnChildClickListener(this); - mExpandableList.setOnGroupExpandListener(this); - mExpandableList.setOnGroupCollapseListener(this); - - if (mFinishedStart) { - setListAdapter(mAdapter); - } - mFinishedStart = true; - } - - /** - * Get the activity's expandable list view widget. This can be used to get the selection, set - * the selection, and many other useful functions. - * - * @see ExpandableListView - */ - public ExpandableListView getExpandableListView() { - ensureList(); - return mExpandableList; - } - - /** - * Get the ExpandableListAdapter associated with this activity's ExpandableListView. - */ - public ExpandableListAdapter getExpandableListAdapter() { - return mAdapter; - } - - /** - * Gets the ID of the currently selected group or child. - * - * @return The ID of the currently selected group or child. - */ - public long getSelectedId() { - return mExpandableList.getSelectedId(); - } - - /** - * Gets the position (in packed position representation) of the currently selected group or - * child. Use {@link ExpandableListView#getPackedPositionType}, - * {@link ExpandableListView#getPackedPositionGroup}, and - * {@link ExpandableListView#getPackedPositionChild} to unpack the returned packed position. - * - * @return A packed position representation containing the currently selected group or child's - * position and type. - */ - public long getSelectedPosition() { - return mExpandableList.getSelectedPosition(); - } - - /** - * Sets the selection to the specified child. If the child is in a collapsed group, the group - * will only be expanded and child subsequently selected if shouldExpandGroup is set to true, - * otherwise the method will return false. - * - * @param groupPosition - * The position of the group that contains the child. - * @param childPosition - * The position of the child within the group. - * @param shouldExpandGroup - * Whether the child's group should be expanded if it is collapsed. - * @return Whether the selection was successfully set on the child. - */ - public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) { - return mExpandableList.setSelectedChild(groupPosition, childPosition, shouldExpandGroup); - } - - /** - * Sets the selection to the specified group. - * - * @param groupPosition - * The position of the group that should be selected. - */ - public void setSelectedGroup(int groupPosition) { - mExpandableList.setSelectedGroup(groupPosition); - } -} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java index b33dbe4c5..1f9605fb1 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java @@ -190,7 +190,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor keySizeAdapter .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); keySize.setAdapter(keySizeAdapter); - keySize.setSelection(2); // Default to 2048 for the key length + keySize.setSelection(3); // Default to 4096 for the key length dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface di, int id) { diff --git a/README.md b/README.md index df2e72850..ee97fe0d1 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,8 @@ I am happy about every code contribution and appreciate your effort to help us d Android Studio is currently not supported or recommended! 1. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/ActionBarSherlock" -2. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/HtmlTextView" -3. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/StickyListHeaders/library" -4. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/zxing" -5. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/AndroidBootstrap" -6. File -> Import -> Android -> Existing Android Code Into Workspace, choose "OpenPGP-Keychain" -7. OpenPGP-Kechain can now be build +2. Repeat step 1 with "libraries/HtmlTextView", "libraries/StickyListHeaders/library", "libraries/AndroidBootstrap", "libraries/zxing", "libraries/zxing-android-integration", "OpenPGP-Keychain" +3. Now all required source files are available in Eclipse # Keychain API @@ -109,18 +105,16 @@ TODO # Libraries -## Build Barcode Scanner Integration +## ZXing Barcode Scanner Android Integration + +Classes can be found under "libraries/zxing-android-integration/". 1. Checkout their SVN (see http://code.google.com/p/zxing/source/checkout) -2. Change android-home variable in "build.properties" in the main directory to point to your Android SDK -3. Change directory to android-integration -4. Build using ``ant build`` -5. We use "android-integration-supportv4.jar" - -On error see: http://code.google.com/p/zxing/issues/detail?id=1207 +2. Copy all classes from their android-integration folder to our library folder ## ZXing +Classes can be found under "libraries/zxing/". ZXing classes were extracted from the ZXing library (http://code.google.com/p/zxing/). Only classes related to QR Code generation are utilized. diff --git a/libraries/zxing-android-integration/.gitignore b/libraries/zxing-android-integration/.gitignore new file mode 100644 index 000000000..71c11b159 --- /dev/null +++ b/libraries/zxing-android-integration/.gitignore @@ -0,0 +1,30 @@ +#Android specific +bin +gen +obj +libs/armeabi +lint.xml +local.properties +release.properties +ant.properties +*.class +*.apk + +#Gradle +.gradle +build +gradle.properties + +#Maven +target +pom.xml.* + +#Eclipse +.project +.classpath +.settings +.metadata + +#IntelliJ IDEA +.idea +*.iml diff --git a/libraries/zxing-android-integration/AndroidManifest.xml b/libraries/zxing-android-integration/AndroidManifest.xml new file mode 100644 index 000000000..c3a6ba464 --- /dev/null +++ b/libraries/zxing-android-integration/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/libraries/zxing-android-integration/build.gradle b/libraries/zxing-android-integration/build.gradle new file mode 100644 index 000000000..21050fc98 --- /dev/null +++ b/libraries/zxing-android-integration/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'android-library' + +android { + compileSdkVersion 19 + buildToolsVersion '19.0.0' + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } +} diff --git a/libraries/zxing-android-integration/project.properties b/libraries/zxing-android-integration/project.properties new file mode 100644 index 000000000..91d2b0246 --- /dev/null +++ b/libraries/zxing-android-integration/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-19 +android.library=true diff --git a/libraries/zxing-android-integration/src/com/google/zxing/integration/android/IntentIntegrator.java b/libraries/zxing-android-integration/src/com/google/zxing/integration/android/IntentIntegrator.java new file mode 100644 index 000000000..4980e9e7e --- /dev/null +++ b/libraries/zxing-android-integration/src/com/google/zxing/integration/android/IntentIntegrator.java @@ -0,0 +1,434 @@ +/* + * Copyright 2009 ZXing authors + * + * 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.google.zxing.integration.android; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; + +/** + *

A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple + * way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the + * project's source code.

+ * + *

Initiating a barcode scan

+ * + *

To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait + * for the result in your app.

+ * + *

It does require that the Barcode Scanner (or work-alike) application is installed. The + * {@link #initiateScan()} method will prompt the user to download the application, if needed.

+ * + *

There are a few steps to using this integration. First, your {@link Activity} must implement + * the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:

+ * + *
{@code
+ * public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ *   IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
+ *   if (scanResult != null) {
+ *     // handle scan result
+ *   }
+ *   // else continue with any other code you need in the method
+ *   ...
+ * }
+ * }
+ * + *

This is where you will handle a scan result.

+ * + *

Second, just call this in response to a user action somewhere to begin the scan process:

+ * + *
{@code
+ * IntentIntegrator integrator = new IntentIntegrator(yourActivity);
+ * integrator.initiateScan();
+ * }
+ * + *

Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the + * user was prompted to download the application. This lets the calling app potentially manage the dialog. + * In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()} + * method.

+ * + *

You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use + * {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and + * yes/no button labels can be changed.

+ * + *

Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used + * to invoke the scanner. This can be used to set additional options not directly exposed by this + * simplified API.

+ * + *

By default, this will only allow applications that are known to respond to this intent correctly + * do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}. + * For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.

+ * + *

Sharing text via barcode

+ * + *

To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.

+ * + *

Some code, particularly download integration, was contributed from the Anobiit application.

+ * + *

Enabling experimental barcode formats

+ * + *

Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as + * PDF417. Use {@link #initiateScan(java.util.Collection)} with + * a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such + * formats.

+ * + * @author Sean Owen + * @author Fred Lin + * @author Isaac Potoczny-Jones + * @author Brad Drehmer + * @author gcstang + */ +public class IntentIntegrator { + + public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits + private static final String TAG = IntentIntegrator.class.getSimpleName(); + + public static final String DEFAULT_TITLE = "Install Barcode Scanner?"; + public static final String DEFAULT_MESSAGE = + "This application requires Barcode Scanner. Would you like to install it?"; + public static final String DEFAULT_YES = "Yes"; + public static final String DEFAULT_NO = "No"; + + private static final String BS_PACKAGE = "com.google.zxing.client.android"; + private static final String BSPLUS_PACKAGE = "com.srowen.bs.android"; + + // supported barcode formats + public static final Collection PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14"); + public static final Collection ONE_D_CODE_TYPES = + list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128", + "ITF", "RSS_14", "RSS_EXPANDED"); + public static final Collection QR_CODE_TYPES = Collections.singleton("QR_CODE"); + public static final Collection DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX"); + + public static final Collection ALL_CODE_TYPES = null; + + public static final List TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE); + public static final List TARGET_ALL_KNOWN = list( + BSPLUS_PACKAGE, // Barcode Scanner+ + BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple + BS_PACKAGE // Barcode Scanner + // What else supports this intent? + ); + + private final Activity activity; + private String title; + private String message; + private String buttonYes; + private String buttonNo; + private List targetApplications; + private final Map moreExtras; + + public IntentIntegrator(Activity activity) { + this.activity = activity; + title = DEFAULT_TITLE; + message = DEFAULT_MESSAGE; + buttonYes = DEFAULT_YES; + buttonNo = DEFAULT_NO; + targetApplications = TARGET_ALL_KNOWN; + moreExtras = new HashMap(3); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setTitleByID(int titleID) { + title = activity.getString(titleID); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setMessageByID(int messageID) { + message = activity.getString(messageID); + } + + public String getButtonYes() { + return buttonYes; + } + + public void setButtonYes(String buttonYes) { + this.buttonYes = buttonYes; + } + + public void setButtonYesByID(int buttonYesID) { + buttonYes = activity.getString(buttonYesID); + } + + public String getButtonNo() { + return buttonNo; + } + + public void setButtonNo(String buttonNo) { + this.buttonNo = buttonNo; + } + + public void setButtonNoByID(int buttonNoID) { + buttonNo = activity.getString(buttonNoID); + } + + public Collection getTargetApplications() { + return targetApplications; + } + + public final void setTargetApplications(List targetApplications) { + if (targetApplications.isEmpty()) { + throw new IllegalArgumentException("No target applications"); + } + this.targetApplications = targetApplications; + } + + public void setSingleTargetApplication(String targetApplication) { + this.targetApplications = Collections.singletonList(targetApplication); + } + + public Map getMoreExtras() { + return moreExtras; + } + + public final void addExtra(String key, Object value) { + moreExtras.put(key, value); + } + + /** + * Initiates a scan for all known barcode types. + */ + public final AlertDialog initiateScan() { + return initiateScan(ALL_CODE_TYPES); + } + + /** + * Initiates a scan only for a certain set of barcode types, given as strings corresponding + * to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants + * like {@link #PRODUCT_CODE_TYPES} for example. + * + * @return the {@link AlertDialog} that was shown to the user prompting them to download the app + * if a prompt was needed, or null otherwise + */ + public final AlertDialog initiateScan(Collection desiredBarcodeFormats) { + Intent intentScan = new Intent(BS_PACKAGE + ".SCAN"); + intentScan.addCategory(Intent.CATEGORY_DEFAULT); + + // check which types of codes to scan for + if (desiredBarcodeFormats != null) { + // set the desired barcode types + StringBuilder joinedByComma = new StringBuilder(); + for (String format : desiredBarcodeFormats) { + if (joinedByComma.length() > 0) { + joinedByComma.append(','); + } + joinedByComma.append(format); + } + intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString()); + } + + String targetAppPackage = findTargetAppPackage(intentScan); + if (targetAppPackage == null) { + return showDownloadDialog(); + } + intentScan.setPackage(targetAppPackage); + intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + attachMoreExtras(intentScan); + startActivityForResult(intentScan, REQUEST_CODE); + return null; + } + + /** + * Start an activity. This method is defined to allow different methods of activity starting for + * newer versions of Android and for compatibility library. + * + * @param intent Intent to start. + * @param code Request code for the activity + * @see android.app.Activity#startActivityForResult(Intent, int) + * @see android.app.Fragment#startActivityForResult(Intent, int) + */ + protected void startActivityForResult(Intent intent, int code) { + activity.startActivityForResult(intent, code); + } + + private String findTargetAppPackage(Intent intent) { + PackageManager pm = activity.getPackageManager(); + List availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + if (availableApps != null) { + for (String targetApp : targetApplications) { + if (contains(availableApps, targetApp)) { + return targetApp; + } + } + } + return null; + } + + private static boolean contains(Iterable availableApps, String targetApp) { + for (ResolveInfo availableApp : availableApps) { + String packageName = availableApp.activityInfo.packageName; + if (targetApp.equals(packageName)) { + return true; + } + } + return false; + } + + private AlertDialog showDownloadDialog() { + AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity); + downloadDialog.setTitle(title); + downloadDialog.setMessage(message); + downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + String packageName; + if (targetApplications.contains(BS_PACKAGE)) { + // Prefer to suggest download of BS if it's anywhere in the list + packageName = BS_PACKAGE; + } else { + // Otherwise, first option: + packageName = targetApplications.get(0); + } + Uri uri = Uri.parse("market://details?id=" + packageName); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + try { + activity.startActivity(intent); + } catch (ActivityNotFoundException anfe) { + // Hmm, market is not installed + Log.w(TAG, "Google Play is not installed; cannot install " + packageName); + } + } + }); + downloadDialog.setNegativeButton(buttonNo, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) {} + }); + return downloadDialog.show(); + } + + + /** + *

Call this from your {@link Activity}'s + * {@link Activity#onActivityResult(int, int, Intent)} method.

+ * + * @return null if the event handled here was not related to this class, or + * else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning, + * the fields will be null. + */ + public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == REQUEST_CODE) { + if (resultCode == Activity.RESULT_OK) { + String contents = intent.getStringExtra("SCAN_RESULT"); + String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT"); + byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES"); + int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE); + Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation; + String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL"); + return new IntentResult(contents, + formatName, + rawBytes, + orientation, + errorCorrectionLevel); + } + return new IntentResult(); + } + return null; + } + + + /** + * Defaults to type "TEXT_TYPE". + * @see #shareText(CharSequence, CharSequence) + */ + public final AlertDialog shareText(CharSequence text) { + return shareText(text, "TEXT_TYPE"); + } + + /** + * Shares the given text by encoding it as a barcode, such that another user can + * scan the text off the screen of the device. + * + * @param text the text string to encode as a barcode + * @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants. + * @return the {@link AlertDialog} that was shown to the user prompting them to download the app + * if a prompt was needed, or null otherwise + */ + public final AlertDialog shareText(CharSequence text, CharSequence type) { + Intent intent = new Intent(); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setAction(BS_PACKAGE + ".ENCODE"); + intent.putExtra("ENCODE_TYPE", type); + intent.putExtra("ENCODE_DATA", text); + String targetAppPackage = findTargetAppPackage(intent); + if (targetAppPackage == null) { + return showDownloadDialog(); + } + intent.setPackage(targetAppPackage); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + attachMoreExtras(intent); + activity.startActivity(intent); + return null; + } + + private static List list(String... values) { + return Collections.unmodifiableList(Arrays.asList(values)); + } + + private void attachMoreExtras(Intent intent) { + for (Map.Entry entry : moreExtras.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // Kind of hacky + if (value instanceof Integer) { + intent.putExtra(key, (Integer) value); + } else if (value instanceof Long) { + intent.putExtra(key, (Long) value); + } else if (value instanceof Boolean) { + intent.putExtra(key, (Boolean) value); + } else if (value instanceof Double) { + intent.putExtra(key, (Double) value); + } else if (value instanceof Float) { + intent.putExtra(key, (Float) value); + } else if (value instanceof Bundle) { + intent.putExtra(key, (Bundle) value); + } else { + intent.putExtra(key, value.toString()); + } + } + } + +} diff --git a/libraries/zxing-android-integration/src/com/google/zxing/integration/android/IntentResult.java b/libraries/zxing-android-integration/src/com/google/zxing/integration/android/IntentResult.java new file mode 100644 index 000000000..2469af92c --- /dev/null +++ b/libraries/zxing-android-integration/src/com/google/zxing/integration/android/IntentResult.java @@ -0,0 +1,95 @@ +/* + * Copyright 2009 ZXing authors + * + * 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.google.zxing.integration.android; + +/** + *

Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.

+ * + * @author Sean Owen + */ +public final class IntentResult { + + private final String contents; + private final String formatName; + private final byte[] rawBytes; + private final Integer orientation; + private final String errorCorrectionLevel; + + IntentResult() { + this(null, null, null, null, null); + } + + IntentResult(String contents, + String formatName, + byte[] rawBytes, + Integer orientation, + String errorCorrectionLevel) { + this.contents = contents; + this.formatName = formatName; + this.rawBytes = rawBytes; + this.orientation = orientation; + this.errorCorrectionLevel = errorCorrectionLevel; + } + + /** + * @return raw content of barcode + */ + public String getContents() { + return contents; + } + + /** + * @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names. + */ + public String getFormatName() { + return formatName; + } + + /** + * @return raw bytes of the barcode content, if applicable, or null otherwise + */ + public byte[] getRawBytes() { + return rawBytes; + } + + /** + * @return rotation of the image, in degrees, which resulted in a successful scan. May be null. + */ + public Integer getOrientation() { + return orientation; + } + + /** + * @return name of the error correction level used in the barcode, if applicable + */ + public String getErrorCorrectionLevel() { + return errorCorrectionLevel; + } + + @Override + public String toString() { + StringBuilder dialogText = new StringBuilder(100); + dialogText.append("Format: ").append(formatName).append('\n'); + dialogText.append("Contents: ").append(contents).append('\n'); + int rawBytesLength = rawBytes == null ? 0 : rawBytes.length; + dialogText.append("Raw bytes: (").append(rawBytesLength).append(" bytes)\n"); + dialogText.append("Orientation: ").append(orientation).append('\n'); + dialogText.append("EC level: ").append(errorCorrectionLevel).append('\n'); + return dialogText.toString(); + } + +} diff --git a/libraries/zxing/.gitignore b/libraries/zxing/.gitignore new file mode 100644 index 000000000..71c11b159 --- /dev/null +++ b/libraries/zxing/.gitignore @@ -0,0 +1,30 @@ +#Android specific +bin +gen +obj +libs/armeabi +lint.xml +local.properties +release.properties +ant.properties +*.class +*.apk + +#Gradle +.gradle +build +gradle.properties + +#Maven +target +pom.xml.* + +#Eclipse +.project +.classpath +.settings +.metadata + +#IntelliJ IDEA +.idea +*.iml diff --git a/settings.gradle b/settings.gradle index 5602503ee..021c54832 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,5 +2,6 @@ include ':OpenPGP-Keychain' include ':libraries:ActionBarSherlock' include ':libraries:HtmlTextView' include ':libraries:StickyListHeaders:library' -include ':libraries:zxing' include ':libraries:AndroidBootstrap' +include ':libraries:zxing' +include ':libraries:zxing-android-integration'