Android recyclerview item focus animation on dpad navigation

 Translation animation, moving in from top, anim/move_in_from_top.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="@android:anim/decelerate_interpolator">
    <translate
            android:fromXDelta="0%" android:toXDelta="0%"
            android:fromYDelta="-100%" android:toYDelta="0%" />
</set>

Translation animation, moving in from bottom, anim/move_in_from_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="@android:anim/decelerate_interpolator">
    <translate
            android:fromXDelta="0%" android:toXDelta="0%"
            android:fromYDelta="100%" android:toYDelta="0%" />
</set>

Translation animation, moving out to the bottom, anim/move_out_down.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="@android:anim/decelerate_interpolator">
    <translate
            android:fromXDelta="0%" android:toXDelta="0%"
            android:fromYDelta="0%" android:toYDelta="100%" />
</set>

Translation animation, moving out to the top, anim/move_out_top.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="@android:anim/decelerate_interpolator">
    <translate
            android:fromXDelta="0%" android:toXDelta="0%"
            android:fromYDelta="0%" android:toYDelta="-100%" />
</set>

The layout file for the list item, list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:background="@color/lightblue"
                android:focusable="true">

    <View android:id="@+id/view_bg"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:background="#000000"
          android:visibility="invisible"/>

    <TextView android:id="@+id/topic"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:textColor="#ffffff"
              android:gravity="center"
              tools:text="Education"/>

</RelativeLayout>

The layout file for the main activity with the RecyclerView, activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                             xmlns:tools="http://schemas.android.com/tools"
                                             android:layout_width="match_parent"
                                             android:layout_height="match_parent"
                                             xmlns:app="http://schemas.android.com/apk/res-auto"
                                             tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="200dp"
            android:layout_height="match_parent"
            android:scrollbars="none"
            android:descendantFocusability="afterDescendants"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

</android.support.constraint.ConstraintLayout>

The RecylerView adapter, this class does all the animation jobs. ItemListAdapter.kt

import android.support.v7.widget.RecyclerView
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.widget.TextView
import android.widget.Toast
import kotlinx.android.synthetic.main.list_item.view.*


class ItemListAdapter(private val itemList: List<String>): RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {

    private var previousFocusedViewHolder: ViewHolder? = null
    private var previouslyFocusedPos = 0
    private var currentlyFocusedPos = 0
    private lateinit var recyclerView: RecyclerView

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutView = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
        return ViewHolder(layoutView)
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        Log.d("ItemListAdapter", "onAttachedToRecyclerView ")
        this.recyclerView = recyclerView
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        Log.d("ItemListAdapter", "onBindViewHolder position: $position")

        holder.itemView.setOnFocusChangeListener { focusedView, hasFocus ->
            Log.d("ItemListAdapter", "onBindViewHolder OnFocusChangeListener position: $position")
            updateFocusPositions(holder, hasFocus, position)
            startFocusAnimation(holder, hasFocus)
        }

        holder.tvTopic.text = itemList[position]
    }

    override fun getItemCount() = itemList.size

    private fun startFocusAnimation(holder: ViewHolder, hasFocus: Boolean) {
        Log.d("ItemListAdapter", "startFocusAnimation hasFocus: $hasFocus, currentlyFocusedPos: $currentlyFocusedPos, previouslyFocusedPos: $previouslyFocusedPos")

        if (hasFocus) {
            previousFocusedViewHolder?.let {
                val moveOutAnimSet = if (currentlyFocusedPos > previouslyFocusedPos) R.anim.move_out_down else R.anim.move_out_up
                val moveOutAnim = AnimationUtils.loadAnimation(it.itemBg.context, moveOutAnimSet)
                moveOutAnim.fillAfter = true
                moveOutAnim.duration = 250
                it.itemBg.startAnimation(moveOutAnim)
                Log.d("ItemListAdapter", "startFocusAnimation, moving out from previousViewHolder: $it")
            }

            val moveInAnimSet = if (currentlyFocusedPos > previouslyFocusedPos) R.anim.move_in_from_top else R.anim.move_in_from_bottom
            val moveInAnim = AnimationUtils.loadAnimation(holder.itemBg.context, moveInAnimSet)
            moveInAnim.fillAfter = true
            moveInAnim.duration = 400
            holder.itemBg.startAnimation(moveInAnim)
            Log.d("ItemListAdapter", "startFocusAnimation, moving into the  currentViewHolder: $holder")
        }
    }

    private fun updateFocusPositions(viewHolder: ViewHolder, hasFocus: Boolean, position: Int) {
        if (hasFocus) {
            previouslyFocusedPos = currentlyFocusedPos
            currentlyFocusedPos = position
        } else {
            previousFocusedViewHolder = viewHolder
        }
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
        var itemBg: View
        var tvTopic: TextView

        init {
            itemView.setOnClickListener(this)
            tvTopic = itemView.topic
            itemBg = itemView.view_bg
        }

        override fun onClick(view: View) {
            Toast.makeText(view.context, "Clicked Position = " + adapterPosition, Toast.LENGTH_SHORT).show()
        }
    }
}

The MainActivity.kt

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private lateinit var linearLayoutManager: LinearLayoutManager
    private lateinit var listAdapter: ItemListAdapter

    val topics = listOf("Education","Finance","Government","Entertainment","Technology","Math","Biology","Physics","Chemistry","Space","Sports","Music","Animal","Countries","Weather","Politics","Traffic","Poverty","Social Media","Internet","Housing")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        linearLayoutManager = LinearLayoutManager(this)
        listAdapter = ItemListAdapter(topics)

        recycler_view.setHasFixedSize(true)
        recycler_view.layoutManager = linearLayoutManager
        recycler_view.adapter = listAdapter
    }

}

Android AlarmManager example with BroadcastReceiver

 1. Create a BroadcastReceiver for receiving the pending intent from AlarmManager.

import android.widget.Toast
import android.content.Intent
import android.content.BroadcastReceiver
import android.content.Context

class MyAlarmReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context, "Alarm Triggered", Toast.LENGTH_LONG).show()
    }
}

2. Register the above BroadcastReceiver in the manifest file inside the application tag.

<receiver android:name=".MyAlarmReceiver"/>

3. Creating the alarm manager and starting it in an Activity.

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.app.AlarmManager
import android.content.Context
import android.app.PendingIntent
import android.content.Intent
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*


class MainActivity: AppCompatActivity() {
    private val REQUEST_CODE = 100
    private lateinit var alarmManager: AlarmManager
    private lateinit var pendingIntent: PendingIntent

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tv_current_time.text = Date().toString()

        // Creating the pending intent to send to the BroadcastReceiver
        alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(this, MyAlarmReceiver::class.java)
        pendingIntent = PendingIntent.getBroadcast(this, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)

        // Setting the specific time for the alarm manager to trigger the intent, in this example, the alarm is set to go off at 23:30, update the time according to your need
        val calendar = Calendar.getInstance()
        calendar.timeInMillis = System.currentTimeMillis()
        calendar.set(Calendar.HOUR_OF_DAY, 23)
        calendar.set(Calendar.MINUTE, 30)

        // Starts the alarm manager
        alarmManager.setRepeating(
            AlarmManager.RTC,
            calendar.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            pendingIntent
        )

    }

    override fun onDestroy() {
        super.onDestroy()
        // Cancels the pendingIntent if it is no longer needed after this activity is destroyed.
        alarmManager.cancel(pendingIntent)
    }
}

Enable TLS 1.1 and 1.2 in Android through OkhttpClient

 TLS1.1 and TLS1.2 is not enabled in Android by default for API 19 or below. If an app is running on Device running Android API 19 or older and trying to make REST request to a server that requires TLS1.1 or above, it will fail.

Checking what versions a server is supporting can be done with nmap, this nmap command line will reveal that.

nmap -sV --script ssl-enum-ciphers github.com

The result of the above should list the TLS versions github supports, as you can see, there is only TlS1.2 is listed from the result as of 11/4/2019, which means Github only supports TLS1.2

Let’s create a simple app making a REST call to github api, here is a post does exactly that.

Then run the app on a device running on API 19 or older, it will fail to get any results from the github REST service because the app is using TLS1.0 and Github requires TLS1.2.

Let’s create a socket factory class to enable TLS1.1 and TLS1.2, TlsSocketFactory.kt

/**
 * Enables TLSv1.1 and TLSv1.2 when creating SSLSockets.
 * References:
 * https://stackoverflow.com/questions/28943660/how-to-enable-tls-1-2-support-in-an-android-application-running-on-android-4-1
 * https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/
 * https://ankushg.com/posts/tls-1.2-on-android/
 * https://github.com/square/okhttp/issues/2372#issuecomment-244807676
 *
 * Android does not support TLS1.1 and TLS1.2 for API 19 or below.
 * @link https://developer.android.com/reference/javax/net/ssl/SSLSocket#protocols
 * @see SSLSocketFactory
 */
class TlsSocketFactory(private val delegate: SSLSocketFactory) : SSLSocketFactory() {

    companion object {
        val ALLOWED_TLS_VERSIONS = arrayOf(TlsVersion.TLS_1_1.javaName(), TlsVersion.TLS_1_2.javaName())
    }

    override fun getDefaultCipherSuites(): Array<String> {
        return delegate.defaultCipherSuites
    }

    override fun getSupportedCipherSuites(): Array<String> {
        return delegate.supportedCipherSuites
    }

    @Throws(IOException::class)
    override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket? {
        return patch(delegate.createSocket(s, host, port, autoClose))
    }

    @Throws(IOException::class, UnknownHostException::class)
    override fun createSocket(host: String, port: Int): Socket? {
        return patch(delegate.createSocket(host, port))
    }

    @Throws(IOException::class, UnknownHostException::class)
    override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket? {
        return patch(delegate.createSocket(host, port, localHost, localPort))
    }

    @Throws(IOException::class)
    override fun createSocket(host: InetAddress, port: Int): Socket? {
        return patch(delegate.createSocket(host, port))
    }

    @Throws(IOException::class)
    override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket? {
        return patch(delegate.createSocket(address, port, localAddress, localPort))
    }

    private fun patch(s: Socket): Socket {
        if (s is SSLSocket) {
            s.enabledProtocols = ALLOWED_TLS_VERSIONS
        }
        return s
    }

}

Now, build a OkhttpClient with the above socket factory to enable TLS1.1 and TLS1.2 to all REST calls that’s going through Okhttp. The important lines here are val httpClientBuilder = createOkhttpClientBuilderWithTlsConfig() and val httpClient = httpClientBuilder.addInterceptor(interceptor).build() and the important function is createOkhttpClientBuilderWithTlsConfig()

class RestUtil private constructor() {

    companion object {
        private val API_BASE_URL = "https://api.github.com/"
        private var self: RestUtil? = null
        val instance: RestUtil
            get() {
                if (self == null) {
                    synchronized(RestUtil::class.java) {
                        if (self == null) {
                            self = RestUtil()
                        }
                    }
                }
                return self!!
            }
    }

    val retrofit: Retrofit

    init {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY
        val httpClientBuilder = createOkhttpClientBuilderWithTlsConfig()
        val httpClient = httpClientBuilder.addInterceptor(interceptor).build()

        val builder = Retrofit.Builder()
            .baseUrl(API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(CoroutineCallAdapterFactory())

        retrofit = builder.client(httpClient).build()
    }

    private fun createOkhttpClientBuilderWithTlsConfig(): OkHttpClient.Builder {
        return OkHttpClient.Builder().apply {
            val trustManager by lazy {
                val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
                trustManagerFactory.init(null as KeyStore?)
                trustManagerFactory.trustManagers.first { it is X509TrustManager } as X509TrustManager
            }
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
                try {
                    val sc = SSLContext.getInstance(TlsVersion.TLS_1_1.javaName())
                    sc.init(null, null, null)
                    sslSocketFactory(TlsSocketFactory(sc.socketFactory), trustManager)

                    val cs = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                        .tlsVersions(*TlsSocketFactory.ALLOWED_TLS_VERSIONS)
                        .build()

                    val specs = ArrayList<ConnectionSpec>()
                    specs.add(cs)
                    specs.add(ConnectionSpec.COMPATIBLE_TLS)
                    specs.add(ConnectionSpec.CLEARTEXT)

                    connectionSpecs(specs)
                } catch (exc: Exception) {
                    Log.e("OkHttpTLSCompat", "Error while setting TLS 1.1 and 1.2", exc)
                }

            }
        }
    }

}

How to extract filename from Uri?

Now, we can extract filename with and without extension :) You will convert your bitmap to uri and get the real path of your file. Now w...