Gitlab@AEI Hannover will go down for maintenance on 2020-09-21 at 4:00 UTC. The service will be unavailable for up to 24 hours. If you have questions about this please contact atlas_admin@aei.mpg.de

Unverified Commit 1aa70fef authored by Vitalii Koshura's avatar Vitalii Koshura Committed by GitHub

Merge pull request #3888 from Isira-Seneviratne/Use_ViewPager2_in_event_log_activity

[Android] Make use of ViewPager2 in EventLogActivity.
parents 20686489 44109496
......@@ -139,7 +139,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'commons-codec:commons-codec:1.14'
implementation 'commons-io:commons-io:2.7'
implementation 'org.apache.commons:commons-lang3:3.10'
......
......@@ -2,131 +2,131 @@
This file is part of BOINC.
http://boinc.berkeley.edu
Copyright (C) 2020 University of California
BOINC is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
BOINC 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with BOINC. If not, see <http://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.berkeley.boinc"
android:installLocation="internalOnly"> <!-- installation on SD card would break boot receiver -->
package="edu.berkeley.boinc"
android:installLocation="internalOnly">
<!-- installation on SD card would break boot receiver -->
<!-- Add Google Play store metadata informing the store we can run on tablets and other large screen devices -->
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:xlargeScreens="true"/>
<!-- Required Permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- Features required for Android TV, consoles, and set-top boxes like Nexus Player, OUYA,
Razer Forge TV, Nvidia SHIELD, etc -->
android:xlargeScreens="true" /> <!-- Required Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--
Features required for Android TV, consoles, and set-top boxes like Nexus Player, OUYA,
Razer Forge TV, Nvidia SHIELD, etc
-->
<!-- Effectively a hint for the Google Play store only and at the Leanback UI Library -->
<uses-feature
android:name="android.software.leanback"
android:required="false"/>
<!-- Implies some sort of D-pad, game controller, joystick, remote control, etc -->
android:required="false" /> <!-- Implies some sort of D-pad, game controller, joystick, remote control, etc -->
<uses-feature
android:name="android.hardware.gamepad"
android:required="false"/>
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false"/>
<!-- Effectively a hint for the Google Play store only -->
android:required="false" /> <!-- Effectively a hint for the Google Play store only -->
<uses-feature
android:name="android.hardware.type.television"
android:required="false"/>
android:required="false" />
<application
android:name=".BOINCApplication"
android:allowBackup="true"
android:banner="@drawable/ic_boinc_logo"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:largeHeap="true"
android:banner="@drawable/ic_boinc_logo"
android:name=".BOINCApplication">
android:theme="@style/AppTheme">
<activity
android:name="edu.berkeley.boinc.SplashActivity"
android:name=".ui.eventlog.EventLogActivity"
android:label="@string/menu_eventlog">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="edu.berkeley.boinc.BOINCActivity" />
</activity>
<activity
android:name=".SplashActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
android:noHistory="true">
android:noHistory="true"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
<category android:name="tv.ouya.intent.category.APP"/>
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
<category android:name="tv.ouya.intent.category.APP" />
</intent-filter>
</activity>
<activity
android:name="edu.berkeley.boinc.BOINCActivity"
android:name=".BOINCActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
android:exported="true"/>
android:launchMode="singleTop" />
<activity
android:name="edu.berkeley.boinc.attach.SelectionListActivity"
android:theme="@style/AppTheme.NoActionBar"/>
android:name=".attach.SelectionListActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name="edu.berkeley.boinc.attach.CredentialInputActivity"
android:name=".attach.CredentialInputActivity"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan"/>
android:windowSoftInputMode="adjustPan" />
<activity
android:name="edu.berkeley.boinc.attach.BatchProcessingActivity"
android:theme="@style/AppTheme.NoActionBar"/>
android:name=".attach.BatchProcessingActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name="edu.berkeley.boinc.attach.BatchConflictListActivity"
android:theme="@style/AppTheme.NoActionBar"/>
<activity android:name="edu.berkeley.boinc.EventLogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="edu.berkeley.boinc.BOINCActivity"/>
</activity>
android:name=".attach.BatchConflictListActivity"
android:theme="@style/AppTheme.NoActionBar" />
<service
android:name="edu.berkeley.boinc.client.Monitor"
android:process=":remote"/>
<service android:name="edu.berkeley.boinc.attach.ProjectAttachService"/>
android:name=".client.Monitor"
android:process=":remote" />
<service android:name=".attach.ProjectAttachService" />
<receiver android:name="edu.berkeley.boinc.receiver.BootReceiver">
<receiver android:name=".receiver.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="edu.berkeley.boinc.receiver.PowerConnectedReceiver">
<receiver android:name=".receiver.PowerConnectedReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
</intent-filter>
</receiver>
<receiver android:name="edu.berkeley.boinc.receiver.PackageReplacedReceiver">
<receiver android:name=".receiver.PackageReplacedReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED"/>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data
android:path="edu.berkeley.boinc"
android:scheme="package"/>
android:scheme="package" />
</intent-filter>
</receiver>
<activity
android:name=".BoincNotExclusiveDialog"
android:theme="@style/Theme.AppCompat.DayNight.Dialog"/>
android:theme="@style/Theme.AppCompat.DayNight.Dialog" />
</application>
</manifest>
......@@ -45,6 +45,7 @@ import edu.berkeley.boinc.client.ClientStatus
import edu.berkeley.boinc.client.IMonitor
import edu.berkeley.boinc.client.Monitor
import edu.berkeley.boinc.databinding.MainBinding
import edu.berkeley.boinc.ui.eventlog.EventLogActivity
import edu.berkeley.boinc.utils.Logging
import edu.berkeley.boinc.utils.RUN_MODE_AUTO
import edu.berkeley.boinc.utils.RUN_MODE_NEVER
......
/*
* This file is part of BOINC.
* http://boinc.berkeley.edu
* Copyright (C) 2020 University of California
*
* BOINC is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* BOINC 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with BOINC. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.berkeley.boinc;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBar.Tab;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import edu.berkeley.boinc.adapter.ClientLogRecyclerViewAdapter;
import edu.berkeley.boinc.adapter.GuiLogRecyclerViewAdapter;
import edu.berkeley.boinc.client.IMonitor;
import edu.berkeley.boinc.client.Monitor;
import edu.berkeley.boinc.rpc.Message;
import edu.berkeley.boinc.utils.Logging;
public class EventLogActivity extends AppCompatActivity {
private IMonitor monitor;
private boolean mIsBound = false;
public RecyclerView clientLogList;
public ClientLogRecyclerViewAdapter clientLogRecyclerViewAdapter;
public List<Message> clientLogData = new ArrayList<>();
public RecyclerView guiLogList;
public GuiLogRecyclerViewAdapter guiLogRecyclerViewAdapter;
public List<String> guiLogData = new ArrayList<>();
private List<EventLogActivityTabListener<?>> listeners = new ArrayList<>();
static final int GUI_LOG_TAB_ACTIVE = 1;
static final int CLIENT_LOG_TAB_ACTIVE = 2;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setup action bar
ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(R.string.menu_eventlog);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
EventLogActivityTabListener<EventLogClientFragment> clientListener =
new EventLogActivityTabListener<>(this, getString(R.string.eventlog_client_header), EventLogClientFragment.class);
listeners.add(clientListener);
Tab tab = actionBar.newTab()
.setText(R.string.eventlog_client_header)
.setTabListener(clientListener);
actionBar.addTab(tab);
EventLogActivityTabListener<EventLogGuiFragment> guiListener =
new EventLogActivityTabListener<>(this, getString(R.string.eventlog_gui_header), EventLogGuiFragment.class);
listeners.add(guiListener);
tab = actionBar.newTab()
.setText(R.string.eventlog_gui_header)
.setTabListener(guiListener);
actionBar.addTab(tab);
actionBar.setDisplayHomeAsUpEnabled(true);
doBindService();
}
@Override
protected void onDestroy() {
doUnbindService();
super.onDestroy();
}
/*
* Service binding part
* only necessary, when function on monitor instance has to be called
*/
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
if(Logging.VERBOSE) {
Log.d(Logging.TAG, "EventLogActivity onServiceConnected");
}
monitor = IMonitor.Stub.asInterface(service);
mIsBound = true;
// initialize default fragment
((EventLogClientFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.eventlog_client_header))).init();
}
@Override
public void onServiceDisconnected(ComponentName className) {
monitor = null;
mIsBound = false;
}
};
private void doBindService() {
if(!mIsBound) {
getApplicationContext().bindService(new Intent(this, Monitor.class), mConnection, 0); //calling within Tab needs getApplicationContext() for bindService to work!
}
}
private void doUnbindService() {
if(mIsBound) {
getApplicationContext().unbindService(mConnection);
mIsBound = false;
}
}
public IMonitor getMonitorService() {
if(!mIsBound && Logging.WARNING) {
Log.w(Logging.TAG, "Fragment trying to obtain service reference, but Monitor not bound in EventLogActivity");
}
return monitor;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.eventlog_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.refresh:
updateCurrentFragment();
return true;
case R.id.email_to:
onEmailTo();
return true;
case R.id.copy:
onCopy();
return true;
}
return super.onOptionsItemSelected(item);
}
private int getActiveLog() {
for(EventLogActivityTabListener<?> tmp : listeners) {
if(tmp.currentlySelected) {
if(tmp.mClass == EventLogClientFragment.class) {
return CLIENT_LOG_TAB_ACTIVE;
}
else if(tmp.mClass == EventLogGuiFragment.class) {
return GUI_LOG_TAB_ACTIVE;
}
}
}
return -1;
}
private void updateCurrentFragment() {
for(EventLogActivityTabListener<?> tmp : listeners) {
if(tmp.currentlySelected) {
if(tmp.mClass == EventLogClientFragment.class) {
((EventLogClientFragment) tmp.mFragment).update();
}
else if(tmp.mClass == EventLogGuiFragment.class) {
((EventLogGuiFragment) tmp.mFragment).update();
}
break;
}
}
}
private void onCopy() {
try {
ClipboardManager clipboard = ContextCompat.getSystemService(this, ClipboardManager.class);
ClipData clipData = ClipData.newPlainText("log", getLogDataAsString());
clipboard.setPrimaryClip(clipData);
Toast.makeText(getApplicationContext(), R.string.eventlog_copy_toast, Toast.LENGTH_SHORT).show();
}
catch(Exception e) {
if(Logging.WARNING) {
Log.w(Logging.TAG, "onCopy failed");
}
}
}
private void onEmailTo() {
try {
String emailText = getLogDataAsString();
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
// Put together the email intent
emailIntent.setType("plain/text");
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.eventlog_email_subject));
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, emailText);
// Send it off to the Activity-Chooser
startActivity(Intent.createChooser(emailIntent, "Send mail..."));
}
catch(Exception e) {
if(Logging.WARNING) {
Log.w(Logging.TAG, "onEmailTo failed");
}
}
}
// returns the content of the log as string
// clientLog = true: client log
// clientlog = false: gui log
private String getLogDataAsString() {
StringBuilder text = new StringBuilder();
int type = getActiveLog();
if(type == CLIENT_LOG_TAB_ACTIVE) {
text.append(getString(R.string.eventlog_client_header)).append("\n\n");
for(int index = 0; index < clientLogList.getChildCount(); index++) {
text.append(clientLogRecyclerViewAdapter.getDateTimeString(index));
text.append("|");
text.append(clientLogRecyclerViewAdapter.getProject(index));
text.append("|");
text.append(clientLogRecyclerViewAdapter.getMessage(index));
text.append("\n");
}
}
else if(type == GUI_LOG_TAB_ACTIVE) {
text.append(getString(R.string.eventlog_gui_header)).append("\n\n");
for(String line : guiLogData) {
text.append(line);
text.append("\n");
}
}
else if(Logging.WARNING) {
Log.w(Logging.TAG, "EventLogActivity could not determine which log active.");
}
return text.toString();
}
}
/*
* This file is part of BOINC.
* http://boinc.berkeley.edu
* Copyright (C) 2013 University of California
*
* BOINC is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* BOINC 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with BOINC. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.berkeley.boinc;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.app.*;
import androidx.appcompat.app.ActionBar.Tab;
public class EventLogActivityTabListener<T extends Fragment> implements ActionBar.TabListener {
public Fragment mFragment;
private final FragmentActivity mActivity;
private final String mTag;
public final Class<T> mClass;
public Boolean currentlySelected = false;
/**
* Constructor used each time a new tab is created.
*
* @param activity The host Activity, used to instantiate the fragment
* @param tag The identifier tag for the fragment
* @param clz The fragment's Class, used to instantiate the fragment
*/
public EventLogActivityTabListener(FragmentActivity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
// Check to see if we already have a fragment for this tab, probably
// from a previously saved state. If so, deactivate it, because our
// initial state is that a tab isn't shown.
mFragment = mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
if(mFragment != null && !mFragment.isDetached()) {
FragmentTransaction ftd = mActivity.getSupportFragmentManager().beginTransaction();
ftd.detach(mFragment);
ftd.commit();
}
}
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// Check if the fragment is already initialized
if(mFragment == null) {
// If not, instantiate and add it to the activity
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
}
else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
currentlySelected = true;
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if(mFragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(mFragment);
}
currentlySelected = false;
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
......@@ -42,6 +42,7 @@ import edu.berkeley.boinc.client.ClientStatus;
import edu.berkeley.boinc.client.IMonitor;
import edu.berkeley.boinc.client.Monitor;
import edu.berkeley.boinc.databinding.ActivitySplashBinding;
import edu.berkeley.boinc.ui.eventlog.EventLogActivity;
import edu.berkeley.boinc.utils.BOINCUtils;
import edu.berkeley.boinc.utils.Logging;
......
/*
* This file is part of BOINC.
* http://boinc.berkeley.edu
* Copyright (C) 2020 University of California
*
* BOINC is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* BOINC 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with BOINC. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.berkeley.boinc.ui.eventlog
import android.content.*
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.tabs.TabLayoutMediator
import edu.berkeley.boinc.R
import edu.berkeley.boinc.adapter.ClientLogRecyclerViewAdapter
import edu.berkeley.boinc.client.IMonitor
import edu.berkeley.boinc.client.Monitor
import edu.berkeley.boinc.databinding.ActivityEventLogBinding
import edu.berkeley.boinc.rpc.Message