Java tutorial
/* * Copyright (c) 2015 IRCCloud, Ltd. * * 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.irccloud.android.activity; import android.app.ActivityManager; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.content.FileProvider; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; import com.crashlytics.android.Crashlytics; import com.google.android.gms.auth.api.Auth; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.irccloud.android.BackgroundTaskService; import com.irccloud.android.ColorScheme; import com.irccloud.android.IRCCloudApplication; import com.irccloud.android.IRCCloudJSONObject; import com.irccloud.android.NetworkConnection; import com.irccloud.android.R; import com.irccloud.android.data.collection.AvatarsList; import com.irccloud.android.data.collection.EventsList; import com.irccloud.android.data.collection.ImageList; import com.irccloud.android.data.model.Server; import com.irccloud.android.data.collection.ServersList; import com.samsung.android.sdk.SsdkUnsupportedException; import com.samsung.android.sdk.multiwindow.SMultiWindow; import com.samsung.android.sdk.multiwindow.SMultiWindowActivity; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class BaseActivity extends AppCompatActivity implements NetworkConnection.IRCEventHandler, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { NetworkConnection conn; private View dialogTextPrompt; private GoogleApiClient mGoogleApiClient; private boolean mResolvingError; private static final int REQUEST_RESOLVE_ERROR = 1001; private static final int REQUEST_SEND_FEEDBACK = 1002; private static final String LOG_FILENAME = "log.txt"; private SMultiWindow mMultiWindow = null; private SMultiWindowActivity mMultiWindowActivity = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); boolean themeChanged = false; String theme = ColorScheme.getUserTheme(); if (ColorScheme.getInstance().theme == null || !ColorScheme.getInstance().theme.equals(theme)) { themeChanged = true; } setTheme(ColorScheme.getTheme(theme, true)); ColorScheme.getInstance().setThemeFromContext(this, theme); if (themeChanged) { EventsList.getInstance().clearCaches(); AvatarsList.getInstance().clear(); } if (Build.VERSION.SDK_INT >= 21) { Bitmap cloud = BitmapFactory.decodeResource(getResources(), R.drawable.splash_logo); if (cloud != null) { setTaskDescription(new ActivityManager.TaskDescription(getResources().getString(R.string.app_name), cloud, ColorScheme.getInstance().navBarColor)); } getWindow().setStatusBarColor(ColorScheme.getInstance().statusBarColor); getWindow().setNavigationBarColor(getResources().getColor(android.R.color.black)); } if (ColorScheme.getInstance().windowBackgroundDrawable != 0) getWindow().setBackgroundDrawableResource(ColorScheme.getInstance().windowBackgroundDrawable); if (Build.VERSION.SDK_INT >= 23) { if (theme.equals("dawn")) getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); else getWindow().getDecorView().setSystemUiVisibility( getWindow().getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } mGoogleApiClient = new GoogleApiClient.Builder(this).addApi(Auth.CREDENTIALS_API) .addConnectionCallbacks(this).addOnConnectionFailedListener(this).build(); conn = NetworkConnection.getInstance(); conn.addHandler(this); if (ServersList.getInstance().count() == 0) NetworkConnection.getInstance().load(); try { mMultiWindow = new SMultiWindow(); mMultiWindow.initialize(this); mMultiWindowActivity = new SMultiWindowActivity(this); } catch (Exception e) { mMultiWindow = null; mMultiWindowActivity = null; } catch (Error e) { mMultiWindow = null; mMultiWindowActivity = null; } } @Override protected void onDestroy() { super.onDestroy(); if (conn != null) { conn.removeHandler(this); } } public boolean isMultiWindow() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return (mMultiWindowActivity != null && !mMultiWindowActivity.isNormalWindow()); else return isInMultiWindowMode(); } @Override protected void onStart() { super.onStart(); BackgroundTaskService.cancelBacklogSync(this); if (!mResolvingError) { mGoogleApiClient.connect(); } } @Override protected void onStop() { mGoogleApiClient.disconnect(); super.onStop(); if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("background_sync", true)) { BackgroundTaskService.scheduleBacklogSync(this); } } @Override public void onConnected(Bundle connectionHint) { } @Override public void onConnectionSuspended(int cause) { } @Override public void onConnectionFailed(ConnectionResult result) { if (mResolvingError) { // Already attempting to resolve an error. return; } else if (result.hasResolution()) { try { mResolvingError = true; result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR); } catch (IntentSender.SendIntentException e) { // There was an error with the resolution intent. Try again. mGoogleApiClient.connect(); } } else { if (GooglePlayServicesUtil.isUserRecoverableError(result.getErrorCode())) { try { GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, REQUEST_RESOLVE_ERROR) .show(); mResolvingError = true; } catch (Exception e) { } } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_RESOLVE_ERROR) { mResolvingError = false; if (resultCode == RESULT_OK) { if (!mGoogleApiClient.isConnecting() && !mGoogleApiClient.isConnected()) { mGoogleApiClient.connect(); } } } else if (requestCode == REQUEST_SEND_FEEDBACK) { if (getFileStreamPath(LOG_FILENAME).exists()) { android.util.Log.d("IRCCloud", "Removing stale log file"); getFileStreamPath(LOG_FILENAME).delete(); } } } public View getDialogTextPrompt() { if (dialogTextPrompt == null) dialogTextPrompt = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)) .inflate(R.layout.dialog_textprompt, null); if (dialogTextPrompt.getParent() != null) ((ViewGroup) dialogTextPrompt.getParent()).removeView(dialogTextPrompt); return dialogTextPrompt; } @Override protected void onPause() { super.onPause(); IRCCloudApplication.getInstance().onPause(this); } @Override public void onResume() { super.onResume(); IRCCloudApplication.getInstance().onResume(this); File f = new File(getFilesDir(), LOG_FILENAME); if (f.exists()) { android.util.Log.d("IRCCloud", "Removing stale log file"); f.delete(); } new ImageListPruneTask().execute((Void) null); String session = getSharedPreferences("prefs", 0).getString("session_key", ""); if (session.length() > 0) { if (conn.notifier) { Crashlytics.log(Log.INFO, "IRCCloud", "Upgrading notifier websocket"); conn.upgrade(); } } else { Intent i = new Intent(this, LoginActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); finish(); } } @Override protected void onPostResume() { super.onPostResume(); if (conn != null) { Crashlytics.log(Log.INFO, "IRCCloud", "App resumed, websocket state: " + conn.getState()); if (conn.getState() == NetworkConnection.STATE_DISCONNECTED || conn.getState() == NetworkConnection.STATE_DISCONNECTING) conn.connect(true); } } public void onIRCEvent(int what, Object obj) { String message = ""; final IRCCloudJSONObject o; switch (what) { case NetworkConnection.EVENT_BADCHANNELKEY: o = (IRCCloudJSONObject) obj; runOnUiThread(new Runnable() { @Override public void run() { Server server = ServersList.getInstance().getServer(o.cid()); AlertDialog.Builder builder = new AlertDialog.Builder(BaseActivity.this); View view = getDialogTextPrompt(); TextView prompt = view.findViewById(R.id.prompt); final EditText keyinput = view.findViewById(R.id.textInput); keyinput.setText(""); keyinput.setOnEditorActionListener(new OnEditorActionListener() { public boolean onEditorAction(TextView textView, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN) { try { if (keyinput.getText() != null) conn.join(o.cid(), o.getString("chan"), keyinput.getText().toString()); } catch (Exception e) { // TODO Auto-generated catch block NetworkConnection.printStackTraceToCrashlytics(e); } ((AlertDialog) keyinput.getTag()).dismiss(); } return true; } }); try { prompt.setText("Password for " + o.getString("chan")); } catch (Exception e) { // TODO Auto-generated catch block NetworkConnection.printStackTraceToCrashlytics(e); } builder.setTitle( server.getName() + " (" + server.getHostname() + ":" + (server.getPort()) + ")"); builder.setView(view); builder.setPositiveButton("Join", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try { conn.join(o.cid(), o.getString("chan"), keyinput.getText().toString()); } catch (Exception e) { // TODO Auto-generated catch block NetworkConnection.printStackTraceToCrashlytics(e); } dialog.dismiss(); } }); builder.setNegativeButton("Cancel", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); AlertDialog dialog = builder.create(); keyinput.setTag(dialog); dialog.setOwnerActivity(BaseActivity.this); dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); dialog.show(); } }); break; case NetworkConnection.EVENT_INVALIDNICK: o = (IRCCloudJSONObject) obj; runOnUiThread(new Runnable() { @Override public void run() { Server server = ServersList.getInstance().getServer(o.cid()); AlertDialog.Builder builder = new AlertDialog.Builder(BaseActivity.this); View view = getDialogTextPrompt(); TextView prompt = view.findViewById(R.id.prompt); final EditText nickinput = view.findViewById(R.id.textInput); nickinput.setText(""); nickinput.setOnEditorActionListener(new OnEditorActionListener() { public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN) { try { conn.say(o.cid(), null, "/nick " + nickinput.getText().toString()); } catch (Exception e) { // TODO Auto-generated catch block NetworkConnection.printStackTraceToCrashlytics(e); } ((AlertDialog) nickinput.getTag()).dismiss(); } return true; } }); try { String message = o.getString("invalid_nick") + " is not a valid nickname, try again"; if (server.isupport != null && server.isupport.has("NICKLEN")) message += "\n(" + server.isupport.get("NICKLEN").asText() + " chars)"; message += "."; prompt.setText(message); } catch (Exception e) { // TODO Auto-generated catch block NetworkConnection.printStackTraceToCrashlytics(e); } builder.setTitle( server.getName() + " (" + server.getHostname() + ":" + (server.getPort()) + ")"); builder.setView(view); builder.setPositiveButton("Change Nickname", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try { conn.say(o.cid(), null, "/nick " + nickinput.getText().toString()); } catch (Exception e) { // TODO Auto-generated catch block NetworkConnection.printStackTraceToCrashlytics(e); } dialog.dismiss(); } }); builder.setNegativeButton("Cancel", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); AlertDialog dialog = builder.create(); nickinput.setTag(dialog); dialog.setOwnerActivity(BaseActivity.this); dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); dialog.show(); } }); break; case NetworkConnection.EVENT_ALERT: try { o = (IRCCloudJSONObject) obj; String type = o.type(); if (type.equalsIgnoreCase("invite_only_chan")) showAlert(o.cid(), "You need an invitation to join " + o.getString("chan")); else if (type.equalsIgnoreCase("channel_full")) showAlert(o.cid(), o.getString("chan") + " isn't allowing any more members to join."); else if (type.equalsIgnoreCase("banned_from_channel")) showAlert(o.cid(), "You've been banned from " + o.getString("chan")); else if (type.equalsIgnoreCase("invalid_nickchange")) showAlert(o.cid(), o.getString("ban_channel") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("no_messages_from_non_registered")) { if (o.has("nick") && o.getString("nick").length() > 0) showAlert(o.cid(), o.getString("nick") + ": " + o.getString("msg")); else showAlert(o.cid(), o.getString("msg")); } else if (type.equalsIgnoreCase("not_registered")) { String first = o.getString("first"); if (o.has("rest")) first += " " + o.getString("rest"); showAlert(o.cid(), first + ": " + o.getString("msg")); } else if (type.equalsIgnoreCase("too_many_channels")) showAlert(o.cid(), "Couldn't join " + o.getString("chan") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("too_many_targets")) showAlert(o.cid(), o.getString("description") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("no_such_server")) showAlert(o.cid(), o.getString("server") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("unknown_command")) showAlert(o.cid(), "Unknown command: " + o.getString("command")); else if (type.equalsIgnoreCase("help_not_found")) showAlert(o.cid(), o.getString("topic") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("accept_exists")) showAlert(o.cid(), o.getString("nick") + " " + o.getString("msg")); else if (type.equalsIgnoreCase("accept_not")) showAlert(o.cid(), o.getString("nick") + " " + o.getString("msg")); else if (type.equalsIgnoreCase("nick_collision")) showAlert(o.cid(), o.getString("collision") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("nick_too_fast")) showAlert(o.cid(), o.getString("nick") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("save_nick")) showAlert(o.cid(), o.getString("nick") + ": " + o.getString("msg") + ": " + o.getString("new_nick")); else if (type.equalsIgnoreCase("unknown_mode")) showAlert(o.cid(), "Missing mode: " + o.getString("params")); else if (type.equalsIgnoreCase("user_not_in_channel")) showAlert(o.cid(), o.getString("nick") + " is not in " + o.getString("channel")); else if (type.equalsIgnoreCase("need_more_params")) showAlert(o.cid(), "Missing parameters for command: " + o.getString("command")); else if (type.equalsIgnoreCase("chan_privs_needed")) showAlert(o.cid(), o.getString("chan") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("not_on_channel")) showAlert(o.cid(), o.getString("channel") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("ban_on_chan")) showAlert(o.cid(), "You cannot change your nick to " + o.getString("proposed_nick") + " while banned on " + o.getString("channel")); else if (type.equalsIgnoreCase("cannot_send_to_chan")) showAlert(o.cid(), o.getString("channel") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("user_on_channel")) showAlert(o.cid(), o.getString("nick") + " is already a member of " + o.getString("channel")); else if (type.equalsIgnoreCase("no_nick_given")) showAlert(o.cid(), "No nickname given"); else if (type.equalsIgnoreCase("nickname_in_use")) showAlert(o.cid(), o.getString("nick") + " is already in use"); else if (type.equalsIgnoreCase("silence")) { String mask = o.getString("usermask"); if (mask.startsWith("-")) message = mask.substring(1) + " removed from silence list"; else if (mask.startsWith("+")) message = mask.substring(1) + " added to silence list"; else message = "Silence list change: " + mask; showAlert(o.cid(), message); } else if (type.equalsIgnoreCase("no_channel_topic")) showAlert(o.cid(), o.getString("channel") + ": " + o.getString("msg")); else if (type.equalsIgnoreCase("time")) { message = o.getString("time_string"); if (o.has("time_stamp") && o.getString("time_stamp").length() > 0) message += " (" + o.getString("time_stamp") + ")"; message += " " + o.getString("time_server"); showAlert(o.cid(), message); } else showAlert(o.cid(), o.getString("msg")); } catch (Exception e1) { NetworkConnection.printStackTraceToCrashlytics(e1); } break; default: break; } } @Override public void onIRCRequestSucceeded(int reqid, IRCCloudJSONObject object) { } @Override public void onIRCRequestFailed(int reqid, IRCCloudJSONObject object) { } protected void showAlert(int cid, final String msg) { final Server server = ServersList.getInstance().getServer(cid); if (server != null) runOnUiThread(new Runnable() { @Override public void run() { AlertDialog.Builder builder = new AlertDialog.Builder(BaseActivity.this); builder.setTitle( server.getName() + " (" + server.getHostname() + ":" + (server.getPort()) + ")"); builder.setMessage(msg); builder.setNegativeButton("Ok", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (IllegalArgumentException e) { } } }); if (!BaseActivity.this.isFinishing()) { AlertDialog dialog = builder.create(); dialog.setOwnerActivity(BaseActivity.this); dialog.show(); } } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_base, menu); setMenuColorFilter(menu); return super.onCreateOptionsMenu(menu); } public void setMenuColorFilter(final Menu menu) { for (int i = 0; i < menu.size(); i++) { MenuItem menuItem = menu.getItem(i); Drawable d = menuItem.getIcon(); if (d != null) { d.mutate(); d.setColorFilter(ColorScheme.getInstance().navBarSubheadingColor, PorterDuff.Mode.SRC_ATOP); } } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_logout: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Logout"); builder.setMessage("Would you like to logout of IRCCloud?"); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.setPositiveButton("Logout", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); conn.logout(); if (mGoogleApiClient.isConnected()) { Auth.CredentialsApi.disableAutoSignIn(mGoogleApiClient) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { Intent i = new Intent(BaseActivity.this, LoginActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); finish(); } }); } else { Intent i = new Intent(BaseActivity.this, LoginActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); finish(); } } }); AlertDialog dialog = builder.create(); dialog.setOwnerActivity(this); dialog.show(); break; case R.id.menu_settings: Intent i = new Intent(this, PreferencesActivity.class); startActivity(i); break; case R.id.menu_feedback: try { String bugReport = "Briefly describe the issue below:\n\n\n\n\n" + "===========\n" + ((NetworkConnection.getInstance().getUserInfo() != null) ? ("UID: " + NetworkConnection.getInstance().getUserInfo().id + "\n") : "") + "App version: " + getPackageManager().getPackageInfo(getPackageName(), 0).versionName + " (" + getPackageManager().getPackageInfo(getPackageName(), 0).versionCode + ")\n" + "Device: " + Build.MODEL + "\n" + "Android version: " + Build.VERSION.RELEASE + "\n" + "Firmware fingerprint: " + Build.FINGERPRINT + "\n"; File logsDir = new File(getFilesDir(), ".Fabric/com.crashlytics.sdk.android.crashlytics-core/log-files/"); File log = null; File output = null; if (logsDir.exists()) { long max = Long.MIN_VALUE; for (File f : logsDir.listFiles()) { if (f.lastModified() > max) { max = f.lastModified(); log = f; } } if (log != null) { File f = new File(getFilesDir(), "logs"); f.mkdirs(); output = new File(f, LOG_FILENAME); byte[] b = new byte[1]; FileOutputStream out = new FileOutputStream(output); FileInputStream is = new FileInputStream(log); is.skip(5); while (is.available() > 0 && is.read(b, 0, 1) > 0) { if (b[0] == ' ') { while (is.available() > 0 && is.read(b, 0, 1) > 0) { out.write(b); if (b[0] == '\n') break; } } } is.close(); out.close(); } } Intent email = new Intent(Intent.ACTION_SEND); email.setData(Uri.parse("mailto:")); email.setType("message/rfc822"); email.putExtra(Intent.EXTRA_EMAIL, new String[] { "IRCCloud Team <team@irccloud.com>" }); email.putExtra(Intent.EXTRA_TEXT, bugReport); email.putExtra(Intent.EXTRA_SUBJECT, "IRCCloud for Android"); if (log != null) { email.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", output)); } startActivityForResult(Intent.createChooser(email, "Send Feedback:"), 0); } catch (Exception e) { Toast.makeText(this, "Unable to generate email report: " + e.getMessage(), Toast.LENGTH_SHORT) .show(); Crashlytics.logException(e); NetworkConnection.printStackTraceToCrashlytics(e); } break; } return super.onOptionsItemSelected(item); } private class ImageListPruneTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { ImageList.getInstance().prune(); return null; } } }