Skip to content

Commit

Permalink
[UITracker] Display current activity name when possible
Browse files Browse the repository at this point in the history
In addition to displaying class name and its hierarchies, the window will now
display the activity name when there's is an activity (not all windows are
activities). This requires the usage stats permission which is enforced here
even if it's disabled in the settings. This is because this feature is meant to
be used as a useful tool separated from the rest of the app, but with some level
of integration.

Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
  • Loading branch information
MuntashirAkon committed Nov 6, 2024
1 parent e6e325a commit 45ff4f8
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.accessibility.AccessibilityMultiplexer;
import io.github.muntashirakon.AppManager.accessibility.NoRootAccessibilityService;
import io.github.muntashirakon.AppManager.self.SelfPermissions;
import io.github.muntashirakon.AppManager.settings.FeatureController;
import io.github.muntashirakon.AppManager.utils.ThreadUtils;

public class LeadingActivityTrackerActivity extends BaseActivity {
private final ActivityResultLauncher<Intent> mSettingsLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
// Init again
init();
});
private final ActivityResultLauncher<Intent> mUsageAccessSettingsLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
// Init again
init();
});

@Override
protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
Expand All @@ -51,6 +59,18 @@ private void init() {
.show();
return;
}
if (!SelfPermissions.checkUsageStatsPermission()) {
ThreadUtils.postOnMainThread(() -> new MaterialAlertDialogBuilder(this)
.setTitle(R.string.grant_usage_access)
.setMessage(R.string.grant_usage_acess_message)
.setCancelable(false)
.setPositiveButton(R.string.go, (dialog, which) -> {
mUsageAccessSettingsLauncher.launch(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
})
.setNegativeButton(R.string.go_back, (dialog, which) -> finish())
.show());
return;
}
if (!NoRootAccessibilityService.isAccessibilityEnabled(this)) {
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.grant_required_permission)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package io.github.muntashirakon.AppManager.accessibility.activity;

import android.annotation.SuppressLint;
import android.app.usage.UsageEvents;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
Expand All @@ -29,11 +30,13 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Future;

import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.accessibility.AccessibilityMultiplexer;
import io.github.muntashirakon.AppManager.compat.UsageStatsManagerCompat;
import io.github.muntashirakon.AppManager.details.AppDetailsActivity;
import io.github.muntashirakon.AppManager.utils.ThreadUtils;
import io.github.muntashirakon.AppManager.utils.UIUtils;
Expand All @@ -48,6 +51,7 @@ public class TrackerWindow implements View.OnTouchListener {
private final ShapeableImageView mIconView;
private final MaterialCardView mContentView;
private final TextInputTextView mPackageNameView;
private final TextInputTextView mActivityNameView;
private final TextInputTextView mClassNameView;
private final TextInputTextView mClassHierarchyView;
private final MaterialButton mPlayPauseButton;
Expand Down Expand Up @@ -83,6 +87,7 @@ public TrackerWindow(@NonNull Context context) {
mIconView = mView.findViewById(R.id.icon);
mContentView = mView.findViewById(R.id.content);
mPackageNameView = mView.findViewById(R.id.package_name);
mActivityNameView = mView.findViewById(R.id.activity_name);
mClassNameView = mView.findViewById(R.id.class_name);
mClassHierarchyView = mView.findViewById(R.id.class_hierarchy);
mPlayPauseButton = mView.findViewById(R.id.action_play_pause);
Expand All @@ -94,6 +99,14 @@ public TrackerWindow(@NonNull Context context) {
copyText("Package name", packageName);
return true;
});
mActivityNameView.setOnLongClickListener(v -> {
Editable activityName = mActivityNameView.getText();
if (TextUtils.isEmpty(activityName)) {
return false;
}
copyText("Activity name", activityName);
return true;
});
mClassNameView.setOnLongClickListener(v -> {
Editable className = mClassNameView.getText();
if (TextUtils.isEmpty(className)) {
Expand All @@ -120,7 +133,7 @@ public TrackerWindow(@NonNull Context context) {
try {
context.startActivity(appInfoIntent);
} catch (Throwable th) {
UIUtils.displayLongToast(th.getMessage());
UIUtils.displayLongToast("Error: " + th.getMessage());
}
});
mView.findViewById(R.id.mini).setOnClickListener(v -> iconify());
Expand Down Expand Up @@ -191,7 +204,11 @@ public void showOrUpdate(AccessibilityEvent event) {
mClassNameView.setText(event.getClassName());
mClassHierarchyResult = ThreadUtils.postOnBackgroundThread(() -> {
CharSequence classHierarchy = TextUtils.join("\n", getClassHierarchy(event));
ThreadUtils.postOnMainThread(() -> mClassHierarchyView.setText(classHierarchy));
String activityName = getActivityName(event);
ThreadUtils.postOnMainThread(() -> {
mActivityNameView.setText(activityName);
mClassHierarchyView.setText(classHierarchy);
});
});
}
}
Expand Down Expand Up @@ -247,6 +264,38 @@ private void copyText(CharSequence label, CharSequence content) {
Utils.copyToClipboard(mView.getContext(), label, content);
}

@Nullable
public String getActivityName(@NonNull AccessibilityEvent event) {
if (event.getPackageName() == null) {
return null;
}
String packageName = event.getPackageName().toString();
UsageEvents.Event usageEvent = new UsageEvents.Event();
long currentTimeMillis = System.currentTimeMillis();
long timeDiff = 5_000;
int tries = 0;
do {
UsageEvents queryEvents = UsageStatsManagerCompat.queryEvents(currentTimeMillis - timeDiff,
currentTimeMillis, UserHandleHidden.myUserId());
long lastTime = 0L;
String activityName = null;
while (queryEvents.hasNextEvent()) {
queryEvents.getNextEvent(usageEvent);
if (usageEvent.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED
&& Objects.equals(packageName, usageEvent.getPackageName())
&& lastTime < usageEvent.getTimeStamp()) {
lastTime = usageEvent.getTimeStamp();
activityName = usageEvent.getClassName();
}
}
if (activityName != null) {
return activityName;
}
timeDiff *= 60;
} while ((++tries) != 3);
return null;
}

@NonNull
private static List<CharSequence> getClassHierarchy(@NonNull AccessibilityEvent event) {
List<CharSequence> classHierarchies = new ArrayList<>();
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/res/layout/window_activity_tracker.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.AppTheme.TextInputLayout.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/activity_name"
app:hintEnabled="true">

<io.github.muntashirakon.widget.TextInputTextView
android:id="@+id/activity_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:maxLines="1"
android:fontFamily="monospace"
tools:text="io.github.muntashirakon.AppManager" />

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.AppTheme.TextInputLayout.Small"
android:layout_width="match_parent"
Expand Down

0 comments on commit 45ff4f8

Please sign in to comment.