Java tutorial
/* * Copyright (c) 2016 The CyanogenMod Project. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.cyanogenmod.changelog; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.annotation.UiThread; import android.support.annotation.WorkerThread; import android.support.customtabs.CustomTabsClient; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.StreamCorruptedException; import java.lang.ref.WeakReference; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; public class ChangelogActivity extends Activity implements SwipeRefreshLayout.OnRefreshListener { /** * Logcat tag. */ private static final String TAG = "ChangelogActivity"; /** * Content view. */ private SwipeRefreshLayout mSwipeRefreshLayout; /** * RecyclerView used to list all the changes. */ private RecyclerView mRecyclerView; /** * Adapter for the RecyclerView. */ private ChangelogAdapter mAdapter; /** * Dialog showing info about the device. */ private Dialog mInfoDialog; /** * Changelog to show */ private Changelog changelog; /** * Debug flag. Enable/disable cache. */ private static final boolean USE_CACHE = true; /** * Number of changes to obtain, at least. */ private static final int NUMBER_OF_CHANGES = 100; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); changelog = new Changelog(); changelog.setBranch(Device.CM_BRANCH); /* Setup and create Views */ init(); /* Populate RecyclerView with cached data */ if (USE_CACHE) bindCache(); /* Fetch data */ updateChangelog(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.actions, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_device_info: mInfoDialog.show(); break; case R.id.menu_refresh: if (!mSwipeRefreshLayout.isRefreshing()) updateChangelog(); break; case R.id.menu_settings: // TODO: Launch Settings Activity break; } return true; } @Override public void onRefresh() { updateChangelog(); } /** * Utility method. */ private void init() { // Setup SwipeRefreshLayout mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh); // Setup refresh listener which triggers new data loading mSwipeRefreshLayout.setOnRefreshListener(this); // Color scheme of the refresh spinner mSwipeRefreshLayout.setColorSchemeResources(R.color.color_primary_dark, R.color.color_accent); // Setup RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); mRecyclerView.setHasFixedSize(true); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); // Setup divider for RecyclerView items mRecyclerView.addItemDecoration(new Divider(this)); // Setup item animator mRecyclerView.setItemAnimator(null); // Disable to prevent view blinking when refreshing // Setup and initialize RecyclerView adapter mAdapter = new ChangelogAdapter(this, new CopyOnWriteArrayList<Change>()); mRecyclerView.setAdapter(mAdapter); // Setup and initialize info dialog String message = String.format(Locale.getDefault(), "%s %s\n\n%s %s\n\n%s %s\n\n%s %s", getString(R.string.dialog_device_name), Device.DEVICE, getString(R.string.dialog_version), Device.CM_VERSION, getString(R.string.dialog_build_date), Device.BUILD_DATE, getString(R.string.dialog_update_channel), Device.CM_RELEASE_CHANNEL); View infoDialog = getLayoutInflater().inflate(R.layout.info_dialog, mRecyclerView, false); AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_InfoDialog).setView(infoDialog) .setPositiveButton(R.string.dialog_ok, null); TextView dialogMessage = (TextView) infoDialog.findViewById(R.id.info_dialog_message); dialogMessage.setText(message); mInfoDialog = builder.create(); } /** * Update Changelog */ private void updateChangelog() { Log.i(TAG, "Updating Changelog"); if (!Device.isConnected(this)) { Log.w(TAG, "Missing network connection"); Toast.makeText(this, R.string.data_connection_required, Toast.LENGTH_SHORT).show(); mSwipeRefreshLayout.setRefreshing(false); return; } new ChangelogTask(this).execute(); } /** * Read cached data and bind it to the RecyclerView. */ private void bindCache() { try { FileInputStream fileInputStream = new FileInputStream(new File(getCacheDir(), "cache")); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); List<Change> cachedData = new LinkedList<>(); Change temp; while ((temp = (Change) objectInputStream.readObject()) != null) { cachedData.add(temp); } objectInputStream.close(); changelog.setChanges(cachedData); mAdapter.clear(); mAdapter.addAll(changelog.getChanges()); Log.d(TAG, "Restored cache"); } catch (FileNotFoundException e) { Log.w(TAG, "Cache not found."); } catch (EOFException e) { Log.e(TAG, "Error while reading cache! (EOF) "); } catch (StreamCorruptedException e) { Log.e(TAG, "Corrupted cache!"); } catch (IOException e) { Log.e(TAG, "Error while reading cache!"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } private static class ChangelogTask extends AsyncTask<Void, Void, Boolean> { private final WeakReference<Context> contextWeakReference; private ChangelogTask(Context context) { this.contextWeakReference = new WeakReference<>(context); } @UiThread @Override protected void onPreExecute() { final Context context = this.contextWeakReference.get(); if (context != null) { final ChangelogActivity changelogActivity = (ChangelogActivity) contextWeakReference.get(); changelogActivity.mSwipeRefreshLayout.setRefreshing(true); } } @WorkerThread @Override protected Boolean doInBackground(Void... voids) { final Context context = this.contextWeakReference.get(); if (context != null) { final ChangelogActivity changelogActivity = (ChangelogActivity) contextWeakReference.get(); return changelogActivity.changelog.update(NUMBER_OF_CHANGES); } return false; } @UiThread @Override protected void onPostExecute(Boolean isUpdated) { final Context context = this.contextWeakReference.get(); if (context != null) { final ChangelogActivity changelogActivity = (ChangelogActivity) contextWeakReference.get(); if (isUpdated) { changelogActivity.mAdapter.clear(); changelogActivity.mAdapter.addAll(changelogActivity.changelog.getChanges()); changelogActivity.mAdapter.notifyDataSetChanged(); // Update cache if (USE_CACHE) { CacheChangelogTask cacheTask = new CacheChangelogTask(context.getCacheDir()); cacheTask.execute((List) changelogActivity.changelog.getChanges()); } } else { Log.d(TAG, "Nothing changed"); } // Delay refreshing animation just for the show new Handler().postDelayed(new Runnable() { @Override public void run() { changelogActivity.mSwipeRefreshLayout.setRefreshing(false); } }, 300); } } } }