Skip to content

Commit 9d36e9a

Browse files
Add ReportActivity and ReportInfo
This implements the framework to report info to users. This may include reporting failure or result of commands or any exceptions that are raised. The ReportInfo provides 5 fields: - userAction: The user action that was being processed for which the report was generated. - sender: The internal app component that sent the report. - title: The report title. - reportString: The markdown text for the report. - addReportAndDeviceDetails: If set to true, then report and device details will be added to the report. This should provide the basics parameters for showing a report to the user. The ReportActivity also allows user to copy and share the report. In future this can also be used to allow users to easily email or post crash reports to github for Termux app crashes instead of going through logcat.
1 parent 3491956 commit 9d36e9a

15 files changed

+386
-7
lines changed

app/src/main/AndroidManifest.xml

+6
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@
101101
android:label="@string/title_activity_termux_settings"
102102
android:theme="@style/Theme.AppCompat.Light.DarkActionBar" />
103103

104+
<activity
105+
android:name=".app.activities.ReportActivity"
106+
android:theme="@style/Theme.AppCompat.TermuxReportActivity"
107+
android:documentLaunchMode="intoExisting"
108+
/>
109+
104110
<activity
105111
android:name=".filepicker.TermuxFileReceiverActivity"
106112
android:excludeFromRecents="true"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package com.termux.app.activities;
2+
3+
import androidx.annotation.NonNull;
4+
import androidx.appcompat.app.ActionBar;
5+
import androidx.appcompat.app.AppCompatActivity;
6+
import androidx.appcompat.widget.Toolbar;
7+
import androidx.recyclerview.widget.LinearLayoutManager;
8+
import androidx.recyclerview.widget.RecyclerView;
9+
10+
import android.content.Context;
11+
import android.content.Intent;
12+
import android.os.Bundle;
13+
import android.view.Menu;
14+
import android.view.MenuInflater;
15+
import android.view.MenuItem;
16+
17+
import com.termux.R;
18+
import com.termux.app.TermuxConstants;
19+
import com.termux.app.utils.MarkdownUtils;
20+
import com.termux.app.utils.ShareUtils;
21+
import com.termux.app.utils.TermuxUtils;
22+
import com.termux.models.ReportInfo;
23+
24+
import org.commonmark.node.FencedCodeBlock;
25+
26+
import io.noties.markwon.Markwon;
27+
import io.noties.markwon.recycler.MarkwonAdapter;
28+
import io.noties.markwon.recycler.SimpleEntry;
29+
30+
public class ReportActivity extends AppCompatActivity {
31+
32+
private static final String ARG_REPORT_INFO = "report_info";
33+
34+
ReportInfo mReportInfo;
35+
36+
@Override
37+
protected void onCreate(Bundle savedInstanceState) {
38+
super.onCreate(savedInstanceState);
39+
setContentView(R.layout.activity_report);
40+
41+
Toolbar toolbar = findViewById(R.id.toolbar);
42+
if (toolbar != null) {
43+
setSupportActionBar(toolbar);
44+
}
45+
46+
Bundle bundle = null;
47+
Intent intent = getIntent();
48+
if(intent != null)
49+
bundle = intent.getExtras();
50+
else if(savedInstanceState != null)
51+
bundle = savedInstanceState;
52+
53+
updateUI(bundle);
54+
55+
}
56+
57+
@Override
58+
protected void onNewIntent(Intent intent) {
59+
super.onNewIntent(intent);
60+
setIntent(intent);
61+
62+
if(intent != null)
63+
updateUI(intent.getExtras());
64+
}
65+
66+
private void updateUI(Bundle bundle) {
67+
68+
if (bundle == null) {
69+
finish();
70+
return;
71+
}
72+
73+
mReportInfo = (ReportInfo) bundle.getSerializable(ARG_REPORT_INFO);
74+
75+
if (mReportInfo == null) {
76+
finish();
77+
return;
78+
}
79+
80+
81+
final ActionBar actionBar = getSupportActionBar();
82+
if (actionBar != null) {
83+
if (mReportInfo.reportTitle != null)
84+
actionBar.setTitle(mReportInfo.reportTitle);
85+
else
86+
actionBar.setTitle(TermuxConstants.TERMUX_APP_NAME + " App Report");
87+
}
88+
89+
90+
RecyclerView recyclerView = findViewById(R.id.recycler_view);
91+
92+
final Markwon markwon = MarkdownUtils.getRecyclerMarkwonBuilder(this);
93+
94+
final MarkwonAdapter adapter = MarkwonAdapter.builderTextViewIsRoot(R.layout.activity_report_adapter_node_default)
95+
.include(FencedCodeBlock.class, SimpleEntry.create(R.layout.activity_report_adapter_node_code_block, R.id.text_view))
96+
.build();
97+
98+
recyclerView.setLayoutManager(new LinearLayoutManager(this));
99+
recyclerView.setAdapter(adapter);
100+
101+
adapter.setMarkdown(markwon, mReportInfo.reportString + getReportAndDeviceDetailsMarkdownString());
102+
adapter.notifyDataSetChanged();
103+
}
104+
105+
106+
107+
@Override
108+
public void onSaveInstanceState(@NonNull Bundle outState) {
109+
super.onSaveInstanceState(outState);
110+
111+
outState.putSerializable(ARG_REPORT_INFO, mReportInfo);
112+
}
113+
114+
@Override
115+
public boolean onCreateOptionsMenu(final Menu menu) {
116+
final MenuInflater inflater = getMenuInflater();
117+
inflater.inflate(R.menu.menu_report, menu);
118+
return true;
119+
}
120+
121+
@Override
122+
public void onBackPressed() {
123+
// Remove activity from recents menu on back button press
124+
finishAndRemoveTask();
125+
}
126+
127+
@Override
128+
public boolean onOptionsItemSelected(final MenuItem item) {
129+
int id = item.getItemId();
130+
if (id == R.id.menu_item_share_report) {
131+
if (mReportInfo != null)
132+
ShareUtils.shareText(this, getString(R.string.report_text), mReportInfo.reportString);
133+
} else if (id == R.id.menu_item_copy_report) {
134+
if (mReportInfo != null)
135+
ShareUtils.copyTextToClipboard(this, mReportInfo.reportString, null);
136+
}
137+
138+
return false;
139+
}
140+
141+
/**
142+
* Get a markdown {@link String} for {@link #mReportInfo} and device details.
143+
*
144+
* @return Returns the markdown {@link String}.
145+
*/
146+
private String getReportAndDeviceDetailsMarkdownString() {
147+
if(!mReportInfo.addReportAndDeviceDetails) return "";
148+
149+
StringBuilder markdownString = new StringBuilder();
150+
151+
markdownString.append("\n\n### Report And Device Details\n\n");
152+
153+
if (mReportInfo != null) {
154+
markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("User Action", mReportInfo.userAction, "-"));
155+
markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Sender", mReportInfo.sender, "-"));
156+
}
157+
158+
markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Timestamp", TermuxUtils.getCurrentTimeStamp(), "-"));
159+
160+
markdownString.append("\n\n").append(TermuxUtils.getDeviceDetailsMarkdownString(this));
161+
162+
markdownString.append("\n##\n");
163+
164+
return markdownString.toString();
165+
}
166+
167+
168+
169+
public static void startReportActivity(@NonNull final Context context, @NonNull final ReportInfo reportInfo) {
170+
if(context == null) return;
171+
context.startActivity(newInstance(context, reportInfo));
172+
}
173+
174+
public static Intent newInstance(@NonNull final Context context, @NonNull final ReportInfo reportInfo) {
175+
if(context == null) return null;
176+
177+
Intent intent = new Intent(context, ReportActivity.class);
178+
Bundle bundle = new Bundle();
179+
bundle.putSerializable(ARG_REPORT_INFO, reportInfo);
180+
intent.putExtras(bundle);
181+
182+
// Note that ReportActivity task has documentLaunchMode="intoExisting" set in AndroidManifest.xml
183+
// which has equivalent behaviour to the following. The following dynamic way doesn't seem to
184+
// work for notification pending intent, i.e separate task isn't created and activity is
185+
// launched in the same task as TermuxActivity.
186+
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
187+
return intent;
188+
}
189+
190+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.termux.models;
2+
3+
import java.io.Serializable;
4+
5+
public class ReportInfo implements Serializable {
6+
7+
/** The user action that was being processed for which the report was generated. */
8+
public UserAction userAction;
9+
/** The internal app component that sent the report. */
10+
public String sender;
11+
/** The report title. */
12+
public String reportTitle;
13+
/** The markdown text for the report. */
14+
public String reportString;
15+
/** If set to {@code true}, then report and device details will be added to the report. */
16+
public boolean addReportAndDeviceDetails;
17+
18+
public ReportInfo(UserAction userAction, String sender, String reportTitle, String reportString, boolean addReportAndDeviceDetails) {
19+
this.userAction = userAction;
20+
this.sender = sender;
21+
this.reportTitle = reportTitle;
22+
this.reportString = reportString;
23+
this.addReportAndDeviceDetails = addReportAndDeviceDetails;
24+
}
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.termux.models;
2+
3+
public enum UserAction {
4+
5+
PLUGIN_EXECUTION_COMMAND("plugin execution command");
6+
7+
private final String name;
8+
9+
UserAction(final String name) {
10+
this.name = name;
11+
}
12+
13+
public String getName() {
14+
return name;
15+
}
16+
17+
}

app/src/main/res/drawable/ic_copy.xml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<vector android:height="24dp" android:tint="#FFFFFF"
2+
android:viewportHeight="24" android:viewportWidth="24"
3+
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
4+
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
5+
</vector>
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<vector android:height="24dp" android:tint="#FFFFFF"
2+
android:viewportHeight="24" android:viewportWidth="24"
3+
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
4+
<path android:fillColor="@android:color/white" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
5+
</vector>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="match_parent"
5+
android:orientation="vertical">
6+
7+
<include
8+
layout="@layout/toolbar_layout"
9+
android:id="@+id/toolbar_layout"/>
10+
11+
<androidx.recyclerview.widget.RecyclerView
12+
android:id="@+id/recycler_view"
13+
android:layout_width="match_parent"
14+
android:layout_height="wrap_content"
15+
android:clipChildren="false"
16+
android:clipToPadding="false"
17+
android:overScrollMode="never"
18+
android:paddingTop="@dimen/content_padding"
19+
android:paddingBottom="36dip" />
20+
21+
</LinearLayout>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="wrap_content"
5+
android:clipChildren="false"
6+
android:clipToPadding="false"
7+
android:fillViewport="true"
8+
android:paddingLeft="16dip"
9+
android:paddingRight="16dip"
10+
android:scrollbarStyle="outsideInset">
11+
12+
<TextView
13+
android:id="@+id/text_view"
14+
android:layout_width="wrap_content"
15+
android:layout_height="wrap_content"
16+
android:background="@color/background_markdown_code_block"
17+
android:fontFamily="monospace"
18+
android:lineSpacingExtra="2dip"
19+
android:paddingLeft="16dip"
20+
android:paddingTop="8dip"
21+
android:paddingRight="16dip"
22+
android:paddingBottom="8dip"
23+
android:textAppearance="?android:attr/textAppearanceMedium"
24+
android:textSize="12sp"
25+
android:textIsSelectable="true" />
26+
27+
</HorizontalScrollView>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
4+
android:id="@+id/text_view"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content"
7+
android:layout_marginLeft="16dip"
8+
android:layout_marginRight="16dip"
9+
android:breakStrategy="simple"
10+
android:hyphenationFrequency="none"
11+
android:lineSpacingExtra="2dip"
12+
android:paddingTop="8dip"
13+
android:paddingBottom="8dip"
14+
android:textAppearance="?android:attr/textAppearanceMedium"
15+
android:textColor="#000"
16+
android:textSize="12sp"
17+
android:textIsSelectable="true" />
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:id="@+id/toolbar_container"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content"
7+
android:orientation="vertical">
8+
9+
<androidx.appcompat.widget.Toolbar
10+
android:id="@+id/toolbar"
11+
android:layout_width="match_parent"
12+
android:layout_height="?attr/actionBarSize"
13+
android:background="?attr/colorPrimaryDark"
14+
android:gravity="center_vertical"
15+
android:minHeight="?attr/actionBarSize"
16+
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
17+
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
18+
app:titleTextAppearance="@style/Toolbar.Title">
19+
20+
</androidx.appcompat.widget.Toolbar>
21+
22+
</LinearLayout>

app/src/main/res/menu/menu_report.xml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<menu xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto">
4+
5+
<item
6+
android:id="@+id/menu_item_share_report"
7+
android:icon="@drawable/ic_share"
8+
android:title="@string/share"
9+
app:showAsAction="never" />
10+
<item
11+
android:id="@+id/menu_item_copy_report"
12+
android:icon="@drawable/ic_copy"
13+
android:title="@string/copy"
14+
app:showAsAction="never" />
15+
</menu>

app/src/main/res/values/colors.xml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<color name="background_markdown_code_inline">#1F000000</color>
4+
<color name="background_markdown_code_block">#0F000000</color>
5+
</resources>

app/src/main/res/values/dimens.xml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<!-- Default screen margins, per the Android Design guidelines. -->
4+
<dimen name="activity_horizontal_margin">16dp</dimen>
5+
<dimen name="activity_vertical_margin">16dp</dimen>
6+
7+
<dimen name="content_padding">8dip</dimen>
8+
<dimen name="content_padding_double">16dip</dimen>
9+
<dimen name="content_padding_half">4dip</dimen>
10+
</resources>

0 commit comments

Comments
 (0)