Categories
discuss

How do I link my HTML with my jQuery?

Firstly sorry for the post, I’m pretty new to coding, I’ll try and keep it short and sweet.

Simply put, when I include my jQuery code inline, I.E. beneath my HTML, it works fine – the element I am trying to animate ‘hides’ and then ‘shows’ as it should.

However, when I make my own separate jquery.js file and put the code in there, it fails to render.

I’ve got the cdn script from google and included that, alongside a script and src to where my file is located inside my project folder, but still no luck.

Inside my project folder I have a ‘script.js’ folder, and then within that a ‘jquery.js’ file.

Here’s the code:

<head>

  <link rel="stylesheet" type="text/css" href="css/style.css"/>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>

  <script src="script.js/jquery.js"></script>

</head>

<div class="content">
  <h2> Hi there</h2>
  <h3> Take a look...</h3>
</div>

Here’s the jQuery:

<script>

$(document).ready(function() {

$(".content").hide(1000).show(1000);

});

</script>

(Upon ‘inspecting’ the problem in chrome I get an error which says “jquery.js:1 Uncaught SyntaxError: Unexpected token <) – But I can’t see where I am mis-using a ‘<‘.

Thanks in advance, and please feel free to tell me if I have left out anything important.

Answer

You need to remove the <script> tags from your jquery.js file, those are HTML tags that are used for implementing inline JS, the error you are getting is because those tags are not valid JavaScript. Your JS file should just look like this:

$(document).ready(function() {
    $(".content").hide(1000).show(1000);
});

As far as folder naming, there’s nothing wrong with having a period in your folder name, but as others have suggested it would probably be a good idea to remove the .js part from your folder name even though it’s not technically wrong and not what is causing your issue.

Categories
discuss

What does the `dir` in `console.dir` stand for?

I get what it does, and use it all the time. But funnily enough, I’ve yet to find documentation that says what “DIR” actually means. “Directory” doesn’t seem right.

This is not a “What’s the difference between console.log() and console.dir()?” question. It’s simply, “What do the letters DIR actually stand for?”

Answer

It just means “Directory”. Directory is a synonym for index, list, listing, register, catalogue, record, archive, inventory etc. Some of those words might have been a better fit for this function though, like “listing”.

Categories
discuss

Android BottomSheetDialogFragment does not expand completely

I have the following test bottom sheet implementation.

When I set the peekHeight to a value less than 500, it works. After some value, any increase in peek height will not change how the bottom sheet is expanded. It Just remains there to only drag manually. How do we set the peekHeight programmatically to ensure that the bottom sheet is auto expanded to the peek height.

enter image description here

bottom_sheet_dialog_main

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/locUXCoordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:id="@+id/locUXView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:orientation="vertical"
        app:behavior_hideable="false"
        app:behavior_peekHeight="0dp"
        app:layout_behavior="@string/bottom_sheet_behavior">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="1 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="2 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="3 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="4 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="5 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="6 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="7 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="8 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="9 Value" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="First Value" />
    </LinearLayout>
</android.support.design.widget.CoordinatorLayout>

Java code

public class MyBottomSheetDialogFragment extends BottomSheetDialogFragment {

    private static BottomSheetBehavior bottomSheetBehavior;
    private static View bottomSheetInternal;
    private static MyBottomSheetDialogFragment INSTANCE;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;
                CoordinatorLayout coordinatorLayout = (CoordinatorLayout)d.findViewById(R.id.locUXCoordinatorLayout);
                bottomSheetInternal = d.findViewById(R.id.locUXView);
                bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetInternal);
                bottomSheetBehavior.setPeekHeight(bottomSheetInternal.getHeight());
                bottomSheetInternal.requestLayout();
                coordinatorLayout.getLayoutParams().height = bottomSheetInternal.getHeight();
                Toast.makeText(getActivity(), "Height is" + bottomSheetInternal.getHeight() + "  " + coordinatorLayout.getLayoutParams().height, Toast.LENGTH_LONG).show();

            }
        });
        INSTANCE = this;
        return inflater.inflate(R.layout.bottom_sheet_dialog_main, container, false);
    }
}

Answer

By deeper UI inspection, we find that there is another CoordinatorLayout that wraps our coordinator layout. The parent CoordinatorLayout has a FrameLayout with a BottomSheetBehaviorwith the id design_bottom_sheet. The peek height set from our code above was getting constrained due the match_parent height of the FrameLayout with the id design_bottom_sheet

By setting the peek height of the FrameLayout with the id design_bottom_sheet , this issue was resolved

    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            BottomSheetDialog d = (BottomSheetDialog) dialog;
            coordinatorLayout = (CoordinatorLayout) d.findViewById(R.id.locUXCoordinatorLayout);
            bottomSheetInternal = d.findViewById(R.id.locUXView);
            bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetInternal);
            bottomSheetBehavior.setHidable(false);
            BottomSheetBehavior.from((View)coordinatorLayout.getParent()).setPeekHeight(bottomSheetInternal.getHeight());
            bottomSheetBehavior.setPeekHeight(bottomSheetInternal.getHeight());
            coordinatorLayout.getParent().requestLayout();

        }
    });
Categories
discuss

How to Use Unsupported Exception for Lower Platform Version

I have a DialogFragment that handles login and fingerprint authentication for my application. This fragment uses two classes that are exclusive to API 23, KeyGenParameterSpec and KeyPermanentlyInvalidatedException. I had been under the impression that I could use these classes, as long as I check the build version before I try to initialize the classes (outlined here):

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    ...
} else {
    ...
}

But it appears that this is not the case. If I try to run this code on a version prior to API 20, the Dalvik VM rejects the entire class and throws a VerifyError. Though, the code does work for API 20 and greater. How can I use these methods in my code while still allowing the code to be used for previous API levels?

The full stack trace is as follows:

05-31 14:35:50.924 11941-11941/com.example.app E/dalvikvm: Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.app.ui.fragment.util.LoginFragment.createKeyPair
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve new-instance 263 (Landroid/security/keystore/KeyGenParameterSpec$Builder;) in Lcom/example/app/ui/fragment/util/LoginFragment;
05-31 14:35:50.924 11941-11941/com.example.app D/dalvikvm: VFY: replacing opcode 0x22 at 0x000c
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve exception class 265 (Landroid/security/keystore/KeyPermanentlyInvalidatedException;)
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to find exception handler at addr 0x3f
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY:  rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z
05-31 14:35:50.924 11941-11941/cp W/dalvikvm: VFY:  rejecting opcode 0x0d at 0x003f
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY:  rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: Verifier rejected class Lcom/example/app/ui/fragment/util/LoginFragment;
05-31 14:35:50.924 11941-11941/com.example.app D/AndroidRuntime: Shutting down VM
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x9cca9b20)
05-31 14:35:50.934 11941-11941/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.example.app, PID: 11941 java.lang.VerifyError: com/example/app/ui/fragment/util/LoginFragment
            at com.example.app.util.NetworkUtility.login(NetworkUtility.java:41)
            at com.example.app.ui.activity.AbstractNavActivity.onOptionsItemSelected(AbstractNavActivity.java:68)
            at android.app.Activity.onMenuItemSelected(Activity.java:2600)
            at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:403)
            at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:189)
            at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100)
            at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100)
            at android.support.v7.app.ToolbarActionBar$2.onMenuItemClick(ToolbarActionBar.java:69)
            at android.support.v7.widget.Toolbar$1.onMenuItemClick(Toolbar.java:169)
            at android.support.v7.widget.ActionMenuView$MenuBuilderCallback.onMenuItemSelected(ActionMenuView.java:760)
            at android.support.v7.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:811)
            at android.support.v7.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152)
            at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:958)
            at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:948)
            at android.support.v7.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:191)
            at android.widget.AdapterView.performItemClick(AdapterView.java:299)
            at android.widget.AbsListView.performItemClick(AbsListView.java:1113)
            at android.widget.AbsListView$PerformClick.run(AbsListView.java:2904)
            at android.widget.AbsListView$3.run(AbsListView.java:3638)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5017)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
            at dalvik.system.NativeStart.main(Native Method)

Updated with Code

The login() method is just a convenience method to start the LoginFragment:

public static void login(FragmentManager manager) {
     manager.beginTransAction().add(LoginFragment.newInstance(), null).commit();
}

The relevant code is in the LoginFragment itself. Specifically the createKeyPair() and initializeCipher methods:

public class LoginFragment extends DialogFragment
        implements TextView.OnEditorActionListener, FingerprintCallback.Callback {

    ...

    public static LoginFragment newInstance() {
        return newInstance(null);
    }

    public static LoginFragment newInstance(Intent intent) {
        LoginFragment fragment = new LoginFragment();

        Bundle args = new Bundle();
        args.putParcelable(EXTRA_INTENT, intent);
        fragment.setArguments(args);

        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Injector.getContextComponent().inject(this);
        setStyle(STYLE_NO_TITLE, R.style.DialogTheme);
        setRetainInstance(true);
        setCancelable(false);

        mSaveUsernamePreference = mPreferences.getBoolean(getString(R.string.key_auth_username_retain));
        mUseFingerprintPreference = mPreferences.getBoolean(getString(R.string.key_auth_fingerprint));
        mUsernamePreference = mPreferences.getString(getString(R.string.key_auth_username));
        mPasswordPreference = mPreferences.getString(getString(R.string.key_auth_password));
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.dialog_login_container, container, false);
        ButterKnife.bind(this, view);

        mPasswordView.setOnEditorActionListener(this);

        if(!mFingerprintManager.isHardwareDetected()) {
            mUseFingerprintToggle.setVisibility(View.GONE);
        } else {
            mGenerated = initializeKeyPair(false);
        }

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            setStage(isFingerprintAvailable() ? Stage.FINGERPRINT : Stage.CREDENTIALS);
        } else {
            setStage(Stage.CREDENTIALS);
        }

        return view;
    }

    @Override
    public void onResume() {
        super.onResume();

        ...

        if(mStage == Stage.FINGERPRINT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            startListening(initializeCipher(Cipher.DECRYPT_MODE));
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        stopListening();
    }

    ...

    @Override
    public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
        Timber.i("Fingerprint succeeded");
        showFingerprintSuccess();

        mSubscriptions.add(
            mGenerated.subscribeOn(Schedulers.newThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .doOnCompleted(() -> {
                        try {
                            mUsername = mUsernamePreference.get();
                            mPassword = decryptPassword(result.getCryptoObject().getCipher());
                            initLoginAttempt();
                        } catch (IllegalBlockSizeException | BadPaddingException exception) {
                            Timber.e(exception, "Failed to decrypt password");
                        }
                    }).subscribe());
    }

    @Override
    public void onAuthenticationHelp(int messageId, CharSequence message) {
        Timber.i("Fingerprint help id: " + messageId + " message: " + message);
        showFingerprintError(message);
    }

    @Override
    public void onAuthenticationError(int messageId, CharSequence message) {
        Timber.i("Fingerprint error id: " + messageId + " message: " + message);
        if(messageId != 5) {
            showFingerprintError(message);
        }
    }

    @Override
    public void onAuthenticationFailed() {
        Timber.i("Fingerprint failed");
        showFingerprintError(getResources().getString(R.string.msg_fingerprint_error_unknown));
    }

    @OnClick(R.id.button_cancel)
    public void onCancel() {
        dismiss();
    }

    @OnClick(R.id.button_continue)
    public void onContinue() {
        switch (mStage) {
            case CREDENTIALS:
                mUsername = mUsernameView.getText().toString();
                mPassword = mPasswordView.getText().toString();
                initLoginAttempt();
                break;
            case FINGERPRINT:
                setStage(Stage.CREDENTIALS);
                break;
        }
    }

    private void showFingerprintSuccess() {
        int colorAccent = ThemeUtil.getColorAttribute(getContext(), android.R.attr.colorAccent);
        mFingerprintIcon.setImageResource(R.drawable.ic_done_white_24dp);
        mFingerprintIcon.setCircleColor(colorAccent);
        mFingerprintStatus.setText(R.string.msg_fingerprint_success);
        mFingerprintStatus.setTextColor(colorAccent);
    }

    private void showFingerprintError(CharSequence message) {
        int colorError = ContextCompat.getColor(getContext(), R.color.material_deep_orange_600);
        mFingerprintIcon.setImageResource(R.drawable.ic_priority_high_white_24dp);
        mFingerprintIcon.setCircleColor(colorError);
        mFingerprintStatus.setText(message);
        mFingerprintStatus.setTextColor(colorError);
        resetFingerprintStatus();
    }

    private void resetFingerprintStatus() {
        mSubscriptions.add(Observable.timer(1600, TimeUnit.MILLISECONDS)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(finished -> {
                    mFingerprintIcon.setImageResource(R.drawable.ic_fingerprint_white_24dp);
                    mFingerprintIcon.setCircleColor(ContextCompat
                            .getColor(getContext(), R.color.material_blue_gray_500));
                    mFingerprintStatus.setText(R.string.msg_fingerprint_input);
                    mFingerprintStatus.setTextColor(ThemeUtil
                            .getColorAttribute(getContext(), android.R.attr.textColorHint));
                }));
    }

    private void onSaveUsernameChanged(boolean checked) {
        if(!checked) {
            mUseFingerprintToggle.setChecked(false);
        }
    }

    private void onUseFingerprintChanged(boolean checked) {
        if(checked) {
            mSaveUsernameToggle.setChecked(true);

            if(!mFingerprintManager.hasEnrolledFingerprints()) {
                displaySettingsDialog();
                mUseFingerprintToggle.setChecked(false);
            }
        }
    }

    public void setStage(Stage stage) {
        switch (stage) {
            case CREDENTIALS:
                Timber.d("Set stage Credentials");
                mPositiveButton.setText(R.string.btn_login);
                mFingerprintContent.setVisibility(View.GONE);
                mCredentialContent.setVisibility(View.VISIBLE);
                setForm();
                break;
            case FINGERPRINT:
                mPositiveButton.setText(R.string.btn_password);
                mCredentialContent.setVisibility(View.GONE);
                mFingerprintContent.setVisibility(View.VISIBLE);
                break;
        } mStage = stage;
    }

    private void startListening(boolean cipher) {
        Timber.v("Start listening for fingerprint input");
        mCancellationSignal = new CancellationSignal();
        if(cipher) {
            mFingerprintManager.authenticate(new FingerprintManagerCompat.CryptoObject(mCipher),
                    0, mCancellationSignal, new FingerprintCallback(this), null);
        } else {
            setStage(Stage.CREDENTIALS);
        }
    }

    private void stopListening() {
        if(mCancellationSignal != null) {
            mCancellationSignal.cancel();
            mCancellationSignal = null;
        }
    }

    private void setForm() {
        if(mSaveUsernamePreference.isSet() && mSaveUsernamePreference.get()
                && mUsernamePreference.isSet()) {
            mUsernameView.setText(mUsernamePreference.get());
            mUsernameView.setSelectAllOnFocus(true);
            mPasswordView.requestFocus();
        } else {
            mUsernameView.requestFocus();
        }
    }

    public void initLoginAttempt() {
        mProgressBar.setVisibility(View.VISIBLE);
        mAuthenticationService.getLoginForm().subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::onLoginFormResponse, this::onError);
    }

    private void onLoginFormResponse(ResponseBody response) {
        try {
            attemptLogin(LoginForm.parse(response.string()));
        } catch (IOException exception) {
            Timber.w(exception, "Failed to parse login form");
        }
    }

    private void attemptLogin(LoginForm loginForm) {
        mAuthenticationService
                .login(loginForm.getLoginTicket(), loginForm.getExecution(), loginForm.getEventIdentifier(),
                        mUsername, mPassword, loginForm.getSubmitValue())
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::onLoginResponse, this::onError);
    }

    public void onLoginResponse(ResponseBody response) {
        Timber.d("LOGIN RESPONSE");
        try {
            Timber.d(response.string());
        } catch (IOException exception) {
            Timber.w(exception, "Failed to retrieve attemptLogin response");
        }

        mSubscriptions.add(NetworkUtility.getAuthentication()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::onAuthenticationChanged, this::onError));
    }

    public void onAuthenticationChanged(Boolean authenticated) {
        if(authenticated) {
            Timber.d("Authentication success");

            if(mStage == Stage.CREDENTIALS) {
                if (mSaveUsernameToggle.isChecked()) {
                    storeUsername();
                } else {
                    clearUsername();
                }

                if (mUseFingerprintToggle.isChecked()) {
                    mGenerated = initializeKeyPair(true);
                    storePassword();
                } else {
                    clearPassword();
                    finishIntent();
                }
            } else {
                finishIntent();
            }
        } else {
            Timber.d("Authentication failed");
            setStage(Stage.CREDENTIALS);
            mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600));
            mCaptionView.setText(getString(R.string.msg_login_failed));
            mPasswordView.setText("");
        }
    }

    private void finishIntent() {
        mProgressBar.setVisibility(View.INVISIBLE);
        Intent intent = getArguments().getParcelable(EXTRA_INTENT);
        if(intent != null) {
            startActivity(intent);
        } dismiss();
    }

    private void onError(Throwable throwable) {
        Timber.w(throwable, "Login attempt failed");
        mProgressBar.setVisibility(View.INVISIBLE);
        mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600));
        mCaptionView.setText("Login attempt failednPlease check your internet connection and try again");
        mPasswordView.setText("");
    }

    private void storeUsername() {
        String username = mUsernameView.getText().toString();
        mUsernamePreference.set(username);
        if(mPreferences.getBoolean(getString(R.string.key_auth_push), false).get()) {
            UAirship.shared().getPushManager().getNamedUser().setId(username);
        }
    }

    private void clearUsername() {
        UAirship.shared().getPushManager().getNamedUser().setId(null);
        mUsernamePreference.delete();
    }

    private void storePassword() {
        Timber.d("STORE PASSWORD");
        mSubscriptions.add(mGenerated.subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnCompleted(() -> {
                    try {
                        Timber.d("Store password");
                        initializeCipher(Cipher.ENCRYPT_MODE);

                        String password = mPasswordView.getText().toString();
                        byte[] bytes = password.getBytes();
                        byte[] encrypted = mCipher.doFinal(bytes);
                        String encoded = Base64.encodeToString(encrypted, Base64.NO_WRAP);

                        mPasswordPreference.set(encoded);

                        finishIntent();

                    } catch (IllegalBlockSizeException | BadPaddingException exception) {
                        Timber.e(exception, "Failed to encrypt password");
                    }
                }).subscribe());
    }

    private String decryptPassword(Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
        String encoded = mPasswordPreference.get();

        Timber.d("ENCODED STRING " + encoded);

        byte[] encrypted = Base64.decode(encoded, Base64.NO_WRAP);

        byte[] bytes = cipher.doFinal(encrypted);

        return new String(bytes);
    }

    private void clearPassword() {
        mPasswordPreference.delete();
    }

    private boolean isFingerprintAvailable() {
        return mUseFingerprintPreference.isSet() && mUseFingerprintPreference.get()
                && mFingerprintManager.hasEnrolledFingerprints()
                && mSaveUsernamePreference.isSet()
                && mPasswordPreference.isSet();
    }

    private void displaySettingsDialog() {
        new AlertDialog.Builder(getContext())
                .setTitle(R.string.title_dialog_secure_lock)
                .setMessage(R.string.msg_fingerprint_unavailable)
                .setPositiveButton(R.string.btn_settings, (dialog, which) -> {
                    startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS));
                    dialog.dismiss();
                }).setNegativeButton(R.string.btn_cancel, (dialog, which) -> {
            dialog.dismiss();
        }).create().show();
    }

    @TargetApi(Build.VERSION_CODES.M)
    private boolean initializeCipher(int opmode) {
        try {
            mKeyStore.load(null);

            /**
             * A known bug in the Android 6.0 (API Level 23) implementation of Bouncy Castle
             * RSA OAEP causes the cipher to default to an SHA-1 certificate, making the SHA-256
             * certificate of the public key incompatible
             * To work around this issue, explicitly provide a new OAEP specification upon
             * initialization
             * @see <a href="https://code.google.com/p/android/issues/detail?id=197719">Issue 197719</a>
             */
            AlgorithmParameterSpec spec = generateOAEPParameterSpec();
            Key key;

            if(opmode == Cipher.ENCRYPT_MODE) {
                Key publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey();

                /**
                 * A known bug in Android 6.0 (API Level 23) causes user authentication-related
                 * authorizations to be enforced even for public keys
                 * To work around this issue, extract the public key material to use outside of
                 * the Android Keystore
                 * @see <a href="http://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html">KeyGenParameterSpec Known Issues</a>
                 */
                key = KeyFactory.getInstance(publicKey.getAlgorithm())
                        .generatePublic(new X509EncodedKeySpec(publicKey.getEncoded()));
            } else {
                key = mKeyStore.getKey(CIPHER_KEY_ALIAS, null);
            }

            mCipher.init(opmode, key, spec);
            return true;
        } catch (KeyPermanentlyInvalidatedException exception) {
            Timber.w(exception, "Failed to initialize Cipher");
            handleKeyPermanentlyInvalidated();
            return false;
        } catch (IOException | KeyStoreException | UnrecoverableEntryException
                | InvalidKeySpecException | CertificateException | InvalidKeyException
                | NoSuchAlgorithmException | InvalidAlgorithmParameterException exception) {
            throw new RuntimeException("Failed to initialize Cipher", exception);
        }
    }

    private OAEPParameterSpec generateOAEPParameterSpec() {
        return new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
    }

    private void handleKeyPermanentlyInvalidated() {
        mCaptionView.setText(getString(R.string.msg_fingerprint_invalidated));
        mGenerated = initializeKeyPair(true);
        clearPassword();
    }

    private Observable<KeyPair> initializeKeyPair(boolean generate) {
        return Observable.create(subscriber -> {
            try {
                mKeyStore.load(null);

                if(!generate || mKeyStore.containsAlias(CIPHER_KEY_ALIAS)) {
                    PublicKey publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey();
                    PrivateKey privateKey = (PrivateKey) mKeyStore.getKey(CIPHER_KEY_ALIAS, null);
                    subscriber.onNext(new KeyPair(publicKey, privateKey));
                } else {
                    subscriber.onNext(createKeyPair());
                }

                subscriber.onCompleted();
            } catch (IOException | KeyStoreException | UnrecoverableKeyException
                    | CertificateException | NoSuchAlgorithmException
                    | InvalidAlgorithmParameterException exception) {
                Timber.e(exception, "Failed to generate key pair");
                subscriber.onError(exception);
            }
        });
    }

    @TargetApi(Build.VERSION_CODES.M)
    private KeyPair createKeyPair() throws InvalidAlgorithmParameterException {
        // Set the alias of the entry in Android KeyStore where the key will appear
        // and the constrains (purposes) in the constructor of the Builder
        Timber.d("Initialize key pair");
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(CIPHER_KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT)
                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                        .setUserAuthenticationRequired(true)
                        .build());

        return mKeyPairGenerator.generateKeyPair();
    }

}

Update

Okay, so I figured out it is the KeyPermanentlyInvalidatedException that is causing the error. If I comment out the catch block that handles that exception, the code runs fine on any device. The problem is that I need to be able to handle that exception on devices on API 23+:

catch (KeyPermanentlyInvalidatedException exception) {
    Timber.w(exception, "A new fingerprint was added to the device");
    handleKeyPermanentlyInvalidated();
    return false;
}

Answer

My guess is that either FingerprintCallback.Callback extends an API Level 23+ interface or that LoginFragment has fields that reference API Level 23+ stuff.

Your rule about being able to call API Level 23+ methods safely inside the version guard block is correct. However, you cannot:

  • inherit from classes that do not exist on the device
  • implement interfaces that do not exist on the device
  • have fields whose types do not exist on the device
  • accept constructor or method parameters whose types do not exist on the device (where we actually call these)
  • have method return values whose types do not exist on the device (where we actually call these)

In many cases, we don’t need any of that, in which case just checking Build.VERSION.SDK_INT before calling API Level 23+ methods is sufficient.

If you need to do some of the things in the bulleted list, that’s fine, but then you need to isolate those into classes that you only use on API Level 23+ devices.

So, for example, let’s pretend that the problem is that FingerprintCallback.Callback extends some API Level 23+ interface. Rather than implementing FingerprintCallback.Callback on the LoginFragment, you might implement that as an anonymous inner class, and only execute the code creating that anonymous inner class instance if Build.VERSION.SDK_INT is high enough. Then, you only are referencing FingerprintCallback.Callback on the newer devices, and you should be safe.

Categories
discuss

Hide image from gallery

I need to hide image(not folder) in both file browser and gallery without changing the file location. I tried prefixing the file name with . it hides the image in the file browser but still showing in gallery. Is there any way to hide image from gallery?

Answer

I need to hide image(not folder) in both file browser and gallery
without changing the file location.

My thought over this problem is
You can change the extension(make file without any extension) so media scanner will not able to detect it.

e.g file name us test.png rename it to only test

Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..