FairEmail/app/src/main/java/eu/faircode/email/ActivityBase.java

1050 lines
52 KiB
Java

package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail 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.
FairEmail 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 FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2023 by Marcel Bokhorst (M66B)
*/
import android.Manifest;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import androidx.core.graphics.ColorUtils;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentResultListener;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.PreferenceManager;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
abstract class ActivityBase extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private int themeId;
private Context originalContext;
private boolean visible;
private boolean contacts;
private List<IKeyPressedListener> keyPressedListeners = new ArrayList<>();
private static final long ACTIONBAR_ANIMATION_DURATION = 250L;
@Override
protected void attachBaseContext(Context base) {
originalContext = base;
super.attachBaseContext(ApplicationEx.getLocalizedContext(base));
}
Context getOriginalContext() {
return originalContext;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
EntityLog.log(this, "Activity create " + this.getClass().getName() +
" version=" + BuildConfig.VERSION_NAME + BuildConfig.REVISION +
" process=" + android.os.Process.myPid());
Intent intent = getIntent();
if (intent != null)
EntityLog.log(this, intent +
" extras=" + TextUtils.join(", ", Log.getExtras(intent.getExtras())));
getSupportFragmentManager().registerFragmentLifecycleCallbacks(lifecycleCallbacks, true);
this.contacts = hasPermission(Manifest.permission.READ_CONTACTS);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean secure = prefs.getBoolean("secure", false);
if (secure)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
if (!this.getClass().equals(ActivityMain.class)) {
themeId = FragmentDialogTheme.getTheme(this);
setTheme(themeId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean dark = Helper.isDarkTheme(this);
Window window = getWindow();
View view = window.getDecorView();
int flags = view.getSystemUiVisibility();
if (dark)
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
view.setSystemUiVisibility(flags);
}
}
String requestKey = getRequestKey();
if (!BuildConfig.PLAY_STORE_RELEASE)
EntityLog.log(this, "Listening key=" + requestKey);
getSupportFragmentManager().setFragmentResultListener(requestKey, this, new FragmentResultListener() {
@Override
public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
try {
result.setClassLoader(ApplicationEx.class.getClassLoader());
int requestCode = result.getInt("requestCode");
int resultCode = result.getInt("resultCode");
EntityLog.log(ActivityBase.this, "Received key=" + requestKey +
" request=" + requestCode +
" result=" + resultCode);
Intent data = new Intent();
data.putExtra("args", result);
onActivityResult(requestCode, resultCode, data);
} catch (Throwable ex) {
Log.e(ex);
}
}
});
prefs.registerOnSharedPreferenceChangeListener(this);
int colorPrimaryDark = Helper.resolveColor(this, R.attr.colorPrimaryDark);
try {
Drawable d = getDrawable(R.drawable.baseline_mail_24);
Bitmap bm = Bitmap.createBitmap(
d.getIntrinsicWidth(),
d.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bm);
d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
d.setTint(Color.WHITE);
d.draw(canvas);
int colorPrimary = colorPrimaryDark;
if (colorPrimary != 0 && Color.alpha(colorPrimary) != 255) {
Log.w("Task color primary=" + Integer.toHexString(colorPrimary));
colorPrimary = ColorUtils.setAlphaComponent(colorPrimary, 255);
}
ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(
null, bm, colorPrimary);
setTaskDescription(td);
} catch (Throwable ex) {
Log.e(ex);
}
boolean navbar_colorize = prefs.getBoolean("navbar_colorize", false);
if (navbar_colorize) {
Window window = getWindow();
if (window != null)
window.setNavigationBarColor(colorPrimaryDark);
}
Fragment bfragment = getSupportFragmentManager()
.findFragmentByTag("androidx.biometric.BiometricFragment");
if (bfragment != null) {
Log.e("Orphan BiometricFragment");
getSupportFragmentManager()
.beginTransaction()
.remove(bfragment)
.commitNowAllowingStateLoss();
/*
java.lang.RuntimeException: Unable to start activity ComponentInfo{eu.faircode.email/eu.faircode.email.ActivitySetup}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment androidx.biometric.FingerprintDialogFragment: could not find Fragment constructor
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2957)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4921)
at android.app.ActivityThread.-wrap19(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1702)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6944)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment androidx.biometric.FingerprintDialogFragment: could not find Fragment constructor
at androidx.fragment.app.Fragment.instantiate(SourceFile:8)
at androidx.fragment.app.FragmentContainer.instantiate(SourceFile:1)
at androidx.fragment.app.FragmentManager$3.instantiate(SourceFile:1)
at androidx.fragment.app.FragmentStateManager.<init>(SourceFile:12)
at androidx.fragment.app.FragmentManager.restoreSaveState(SourceFile:11)
at androidx.fragment.app.FragmentController.restoreSaveState(SourceFile:2)
at androidx.fragment.app.FragmentActivity$2.onContextAvailable(SourceFile:5)
at androidx.activity.contextaware.ContextAwareHelper.dispatchOnContextAvailable(SourceFile:3)
at androidx.activity.ComponentActivity.onCreate(SourceFile:2)
at androidx.fragment.app.FragmentActivity.onCreate(SourceFile:1)
at eu.faircode.email.ActivityBase.onCreate(SourceFile:37)
at eu.faircode.email.ActivitySetup.onCreate(SourceFile:1)
at android.app.Activity.performCreate(Activity.java:7183)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910)
... 10 more
Caused by: java.lang.NoSuchMethodException: <init> []
at java.lang.Class.getConstructor0(Class.java:2320)
at java.lang.Class.getConstructor(Class.java:1725)
at androidx.fragment.app.Fragment.instantiate(SourceFile:4)
*/
}
checkAuthentication(true);
super.onCreate(savedInstanceState);
}
@Override
protected void onNewIntent(Intent intent) {
if (intent != null)
EntityLog.log(this, "New " + intent +
" extras=" + TextUtils.join(", ", Log.getExtras(intent.getExtras())));
super.onNewIntent(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
int before = Helper.getSize(outState);
super.onSaveInstanceState(outState);
int after = Helper.getSize(outState);
Log.d("Saved instance " + this + " size=" + before + "/" + after);
Map<String, String> crumb = new HashMap<>();
crumb.put("name", this.getClass().getName());
crumb.put("before", Integer.toString(before));
crumb.put("after", Integer.toString(after));
Log.breadcrumb("onSaveInstanceState", crumb);
for (String key : outState.keySet())
Log.d("Saved " + this + " " + key + "=" + outState.get(key));
}
@Override
protected void onResume() {
Log.d("Resume " + this.getClass().getName());
super.onResume();
visible = true;
if (!(this instanceof ActivityMain)) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putString("last_activity", this.getClass().getName()).apply();
}
boolean contacts = hasPermission(Manifest.permission.READ_CONTACTS);
if (this.contacts != contacts &&
!this.getClass().equals(ActivitySetup.class) &&
!this.getClass().equals(ActivityCompose.class)) {
Log.i("Contacts permission=" + contacts);
finish();
startActivity(getIntent());
} else
checkAuthentication(true);
}
@Override
protected void onPause() {
Log.d("Pause " + this.getClass().getName());
super.onPause();
visible = false;
checkAuthentication(false);
}
@Override
public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
try {
return super.onPreparePanel(featureId, view, menu);
} catch (Throwable ex) {
/*
This should never happen, but ...
java.lang.NullPointerException: Attempt to invoke interface method 'android.view.MenuItem android.view.MenuItem.setEnabled(boolean)' on a null object reference
at eu.faircode.email.FragmentCompose.onPrepareOptionsMenu(SourceFile:3)
at androidx.fragment.app.Fragment.performPrepareOptionsMenu(SourceFile:3)
at androidx.fragment.app.FragmentManager.dispatchPrepareOptionsMenu(SourceFile:3)
at androidx.fragment.app.FragmentManager$2.onPrepareMenu(Unknown Source:2)
at androidx.core.view.MenuHostHelper.onPrepareMenu(SourceFile:2)
at androidx.activity.ComponentActivity.onPrepareOptionsMenu(SourceFile:2)
at android.app.Activity.onPreparePanel(Activity.java:3391)
at androidx.appcompat.view.WindowCallbackWrapper.onPreparePanel(Unknown Source:2)
at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.onPreparePanel(SourceFile:4)
at androidx.appcompat.app.AppCompatDelegateImpl.preparePanel(SourceFile:28)
at androidx.appcompat.app.AppCompatDelegateImpl.doInvalidatePanelMenu(SourceFile:14)
at androidx.appcompat.app.AppCompatDelegateImpl$2.run(SourceFile:2)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:948)
at android.view.Choreographer.doCallbacks(Choreographer.java:750)
at android.view.Choreographer.doFrame(Choreographer.java:679)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:934)
at android.os.Handler.handleCallback(Handler.java:869)
*/
Log.e(ex);
return false;
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.d("Config " + this.getClass().getName());
super.onConfigurationChanged(newConfig);
}
@Override
public void onUserInteraction() {
Log.d("User interaction");
checkAuthentication(true);
}
@Override
protected void onUserLeaveHint() {
Log.d("User leaving");
}
@Override
protected void onStop() {
try {
super.onStop();
} catch (Throwable ex) {
Log.e(ex);
/*
Exception java.lang.RuntimeException:
at android.app.ActivityThread.callActivityOnStop (ActivityThread.java:4178)
at android.app.ActivityThread.performStopActivityInner (ActivityThread.java:4148)
at android.app.ActivityThread.handleStopActivity (ActivityThread.java:4223)
at android.app.servertransaction.StopActivityItem.execute (StopActivityItem.java:41)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:145)
at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1823)
at android.os.Handler.dispatchMessage (Handler.java:107)
at android.os.Looper.loop (Looper.java:198)
at android.app.ActivityThread.main (ActivityThread.java:6729)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:876)
Caused by java.lang.RuntimeException: Failed to call observer method
at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback (ClassesInfoCache.java:232)
at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeMethodsForEvent (ClassesInfoCache.java:199)
at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeCallbacks (ClassesInfoCache.java:191)
at androidx.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged (ReflectiveGenericLifecycleObserver.java:40)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent (LifecycleRegistry.java:360)
at androidx.lifecycle.LifecycleRegistry.backwardPass (LifecycleRegistry.java:290)
at androidx.lifecycle.LifecycleRegistry.sync (LifecycleRegistry.java:308)
at androidx.lifecycle.LifecycleRegistry.moveToState (LifecycleRegistry.java:151)
at androidx.lifecycle.LifecycleRegistry.setCurrentState (LifecycleRegistry.java:121)
at androidx.fragment.app.FragmentViewLifecycleOwner.setCurrentState (FragmentViewLifecycleOwner.java:89)
at androidx.fragment.app.FragmentActivity.markState (FragmentActivity.java:781)
at androidx.fragment.app.FragmentActivity.markFragmentsCreated (FragmentActivity.java:764)
at androidx.fragment.app.FragmentActivity.onStop (FragmentActivity.java:372)
at androidx.appcompat.app.AppCompatActivity.onStop (AppCompatActivity.java:257)
at eu.faircode.email.ActivityBase.onStop (ActivityBase.java:344)
at eu.faircode.email.ActivityView.onStop (ActivityView.java:1064)
at android.app.Instrumentation.callActivityOnStop (Instrumentation.java:1433)
at android.app.Activity.performStop (Activity.java:7367)
at android.app.ActivityThread.callActivityOnStop (ActivityThread.java:4170)
Caused by java.lang.NullPointerException:
at androidx.activity.OnBackPressedDispatcher$OnBackPressedCancellable.cancel (OnBackPressedDispatcher.java:273)
at androidx.activity.OnBackPressedCallback.remove (OnBackPressedCallback.java:101)
at eu.faircode.email.FragmentBase$3.onAny (FragmentBase.java:478)
at java.lang.reflect.Method.invoke (Method.java)
at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback (ClassesInfoCache.java:222)
*/
}
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm != null && !pm.isInteractive()) {
Log.i("Stop with screen off");
if (Helper.shouldAutoLock(this)) {
Helper.clearAuthentication(this);
lock();
}
}
}
@Override
protected void onDestroy() {
Log.i("Destroy " + this.getClass().getName());
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
try {
getSupportFragmentManager().unregisterFragmentLifecycleCallbacks(lifecycleCallbacks);
super.onDestroy();
originalContext = null;
} catch (Throwable ex) {
Log.w(ex);
/*
java.lang.RuntimeException: Unable to destroy activity {eu.faircode.email/eu.faircode.email.ActivityView}: java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4605)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4623)
at android.app.ActivityThread.-wrap5(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1757)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6944)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Caused by: java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0
at android.text.SpannableStringInternal.checkRange(SpannableStringInternal.java:442)
at android.text.SpannableStringInternal.setSpan(SpannableStringInternal.java:163)
at android.text.SpannableStringInternal.setSpan(SpannableStringInternal.java:152)
at android.text.SpannableString.setSpan(SpannableString.java:46)
at android.text.Selection.setSelection(Selection.java:76)
at android.widget.TextView.semSetSelection(TextView.java:13204)
at android.widget.Editor$SelectionModifierCursorController.resetDragAcceleratorState(Editor.java:6547)
at android.widget.Editor$SelectionModifierCursorController.resetTouchOffsets(Editor.java:6537)
at android.widget.Editor$SelectionModifierCursorController.<init>(Editor.java:6172)
at android.widget.Editor.getSelectionController(Editor.java:2449)
at android.widget.Editor.onDetachedFromWindow(Editor.java:470)
at android.widget.TextView.onDetachedFromWindowInternal(TextView.java:7232)
at android.view.View.dispatchDetachedFromWindow(View.java:18677)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3878)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3878)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3878)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3878)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3878)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3878)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3878)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3878)
at android.view.ViewGroup.removeViewInternal(ViewGroup.java:5432)
at android.view.ViewGroup.removeViewInternal(ViewGroup.java:5403)
at android.view.ViewGroup.removeView(ViewGroup.java:5334)
at androidx.fragment.app.SpecialEffectsController$Operation$State.applyState(SourceFile:14)
at androidx.fragment.app.SpecialEffectsController$1.run(SourceFile:2)
at androidx.fragment.app.SpecialEffectsController$Operation.complete(SourceFile:6)
at androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete(SourceFile:1)
at androidx.fragment.app.SpecialEffectsController$Operation.cancel(SourceFile:4)
at androidx.fragment.app.SpecialEffectsController.forceCompleteAllOperations(SourceFile:21)
at androidx.fragment.app.FragmentManager.dispatchStateChange(SourceFile:6)
at androidx.fragment.app.FragmentManager.dispatchDestroy(SourceFile:4)
at androidx.fragment.app.FragmentController.dispatchDestroy(SourceFile:1)
at androidx.fragment.app.FragmentActivity.onDestroy(SourceFile:2)
at androidx.appcompat.app.AppCompatActivity.onDestroy(SourceFile:1)
at eu.faircode.email.ActivityBase.onDestroy(SourceFile:3)
at eu.faircode.email.ActivityBilling.onDestroy(SourceFile:3)
at eu.faircode.email.ActivityView.onDestroy(SourceFile:3)
at android.app.Activity.performDestroy(Activity.java:7522)
*/
}
}
public int getThemeId() {
return this.themeId;
}
public String getRequestKey() {
return this.getClass().getName() + ":activity";
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
EntityLog.log(this, "Result class=" + this.getClass().getSimpleName() +
" action=" + (data == null ? null : data.getAction()) +
" request=" + requestCode +
" result=" + resultCode + " ok=" + (resultCode == RESULT_OK) +
" data=" + (data == null ? null : data.getData()) +
(data == null ? "" : " " + TextUtils.join(" ", Log.getExtras(data.getExtras()))));
super.onActivityResult(requestCode, resultCode, data);
}
private void checkAuthentication(boolean auth) {
if (this.getClass().equals(ActivityMain.class))
return;
if (!Helper.shouldAuthenticate(this, !auth))
return;
lock();
if (auth) {
if (this instanceof ActivityWidget ||
this instanceof ActivityWidgetSync ||
this instanceof ActivityWidgetUnified) {
Toast.makeText(this, R.string.title_notification_redacted, Toast.LENGTH_LONG).show();
} else {
Intent intent = getIntent();
processStreams(intent);
Intent main = new Intent(this, ActivityMain.class)
.putExtra("intent", intent);
main.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(main);
}
}
}
private void lock() {
finishAndRemoveTask();
setResult(RESULT_CANCELED);
finishAffinity();
}
private void processStreams(Intent intent) {
intent.setClipData(null);
if (intent.hasExtra(Intent.EXTRA_STREAM)) {
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (uris == null)
intent.removeExtra(Intent.EXTRA_STREAM);
else {
ArrayList<Uri> processed = new ArrayList<>();
for (Uri uri : uris)
if (uri != null)
processed.add(processUri(uri));
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, processed);
}
} else {
Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (uri == null)
intent.removeExtra(Intent.EXTRA_STREAM);
else
intent.putExtra(Intent.EXTRA_STREAM, processUri(uri));
}
}
}
private Uri processUri(Uri uri) {
try {
String fname = null;
try {
DocumentFile dfile = DocumentFile.fromSingleUri(this, uri);
if (dfile != null)
fname = dfile.getName();
} catch (SecurityException ex) {
Log.e(ex);
}
if (TextUtils.isEmpty(fname))
fname = uri.getLastPathSegment();
if (TextUtils.isEmpty(fname))
return uri;
File dir = Helper.ensureExists(new File(getFilesDir(), "shared"));
File file = new File(dir, fname);
Log.i("Copying shared file to " + file);
InputStream is = getContentResolver().openInputStream(uri);
if (is == null)
throw new FileNotFoundException(uri.toString());
Helper.copy(is, new FileOutputStream(file));
return FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);
} catch (Throwable ex) {
Log.w(ex);
return uri;
}
}
@Override
public void startActivity(Intent intent) {
try {
Log.i("Start intent=" + intent);
Log.logExtras(intent);
super.startActivity(intent);
} catch (Throwable ex) {
if (this instanceof ActivityMain)
throw ex;
if (intent.getPackage() == null)
Helper.reportNoViewer(this, intent, ex);
else {
intent.setPackage(null);
try {
super.startActivity(intent);
} catch (Throwable exex) {
Helper.reportNoViewer(this, intent, exex);
}
}
}
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
try {
Log.i("Start intent=" + intent + " request=" + requestCode);
Log.logExtras(intent);
super.startActivityForResult(intent, requestCode);
} catch (Throwable ex) {
if (intent.getPackage() == null)
Helper.reportNoViewer(this, intent, ex);
else {
intent.setPackage(null);
try {
super.startActivityForResult(intent, requestCode);
} catch (Throwable exex) {
Helper.reportNoViewer(this, intent, exex);
}
}
}
}
@Override
public ComponentName startService(Intent service) {
try {
return super.startService(service);
} catch (Throwable ex) {
Log.w(ex);
/*
Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { act=clear:0 cmp=eu.faircode.email/.ServiceUI }: app is in background uid UidRecord{cb19b35 u0a286 TRNB idle change:uncached procs:1 proclist:1344, seq(0,0,0)}
at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1666)
at android.app.ContextImpl.startService(ContextImpl.java:1611)
at android.content.ContextWrapper.startService(ContextWrapper.java:677)
at android.content.ContextWrapper.startService(ContextWrapper.java:677)
at eu.faircode.email.ActivityView.checkIntent(SourceFile:873)
at eu.faircode.email.ActivityView.onResume(SourceFile:595)
*/
return null;
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
Log.i("Preference " + key + "=" + prefs.getAll().get(key));
if ("theme".equals(key) || "beige".equals(key)) {
finish();
if (visible &&
(this.getClass().equals(ActivitySetup.class) ||
this.getClass().equals(ActivityView.class)))
startActivity(getIntent());
} else if (!this.getClass().equals(ActivitySetup.class) && !visible &&
Arrays.asList(FragmentOptions.OPTIONS_RESTART).contains(key))
finish();
}
public boolean hasPermission(String name) {
return Helper.hasPermission(this, name);
}
void addKeyPressedListener(final IKeyPressedListener listener, LifecycleOwner owner) {
Log.d("Adding back listener=" + listener);
keyPressedListeners.add(listener);
owner.getLifecycle().addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroyed() {
Log.d("Removing back listener=" + listener);
keyPressedListeners.remove(listener);
owner.getLifecycle().removeObserver(this);
}
});
}
public boolean dispatchKeyEvent(KeyEvent event) {
for (IKeyPressedListener listener : keyPressedListeners)
if (listener.onKeyPressed(event))
return true;
try {
return super.dispatchKeyEvent(event);
} catch (Throwable ex) {
Log.w(ex);
/*
java.lang.IllegalArgumentException
at com.android.internal.util.Preconditions.checkArgument(Preconditions.java:33)
at android.widget.SelectionActionModeHelper$TextClassificationHelper.init(SelectionActionModeHelper.java:974)
at android.widget.SelectionActionModeHelper.resetTextClassificationHelper(SelectionActionModeHelper.java:462)
at android.widget.SelectionActionModeHelper.resetTextClassificationHelper(SelectionActionModeHelper.java:470)
at android.widget.SelectionActionModeHelper.startSelectionActionModeAsync(SelectionActionModeHelper.java:118)
at android.widget.Editor.startSelectionActionModeAsync(Editor.java:2131)
at android.widget.Editor.refreshTextActionMode(Editor.java:2076)
at android.widget.TextView.spanChange(TextView.java:9903)
at android.widget.TextView$ChangeWatcher.onSpanChanged(TextView.java:12534)
at android.text.SpannableStringBuilder.sendSpanChanged(SpannableStringBuilder.java:1303)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:750)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:674)
at android.text.Selection.extendSelection(Selection.java:174)
at android.text.Selection.setSelectionAndMemory(Selection.java:256)
at android.text.Selection.extendUp(Selection.java:357)
at android.text.method.ArrowKeyMovementMethod.up(ArrowKeyMovementMethod.java:92)
at android.text.method.BaseMovementMethod.handleMovementKey(BaseMovementMethod.java:189)
at android.text.method.ArrowKeyMovementMethod.handleMovementKey(ArrowKeyMovementMethod.java:65)
at android.text.method.BaseMovementMethod.onKeyDown(BaseMovementMethod.java:42)
at android.widget.TextView.doKeyDown(TextView.java:7691)
at android.widget.TextView.onKeyDown(TextView.java:7442)
at android.view.KeyEvent.dispatch(KeyEvent.java:2692)
at android.view.View.dispatchKeyEvent(View.java:12471)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.widget.ScrollView.dispatchKeyEvent(ScrollView.java:389)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896)
at com.android.internal.policy.DecorView.superDispatchKeyEvent(DecorView.java:451)
at com.android.internal.policy.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1830)
at android.app.Activity.dispatchKeyEvent(Activity.java:3385)
at androidx.core.app.ComponentActivity.superDispatchKeyEvent(SourceFile:122)
at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(SourceFile:84)
at androidx.core.app.ComponentActivity.dispatchKeyEvent(SourceFile:140)
at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(SourceFile:559)
*/
return false;
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
try {
return super.dispatchTouchEvent(ev);
} catch (Throwable ex) {
Log.w(ex);
/*
java.lang.IllegalArgumentException: captureChildView: parameter must be a descendant of the ViewDragHelper's tracked parent view (androidx.coordinatorlayout.widget.CoordinatorLayout{35ad956 V.E...... ........ 0,0-1080,2100})
at androidx.customview.widget.ViewDragHelper.captureChildView(ViewDragHelper:472)
at androidx.customview.widget.ViewDragHelper.tryCaptureViewForDrag(ViewDragHelper:914)
at androidx.customview.widget.ViewDragHelper.processTouchEvent(ViewDragHelper:1155)
at com.google.android.material.behavior.SwipeDismissBehavior.onTouchEvent(SwipeDismissBehavior:215)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onTouchEvent(CoordinatorLayout:563)
at android.view.View.dispatchTouchEvent(View.java:13483)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3082)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2767)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3088)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2781)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:496)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1853)
at android.app.Activity.dispatchTouchEvent(Activity.java:4059)
at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper:69)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:454)
at android.view.View.dispatchPointerEvent(View.java:13744)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5635)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5435)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4936)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4989)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4955)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5095)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4963)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5152)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4936)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4989)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4955)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4963)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4936)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7688)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7657)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7618)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7818)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:251)
at android.os.MessageQueue.nativePollOnce(MessageQueue.java:-2)
at android.os.MessageQueue.next(MessageQueue.java:336)
at android.os.Looper.loop(Looper.java:181)
at android.app.ActivityThread.main(ActivityThread.java:7562)
*/
return false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
return super.onTouchEvent(event);
} catch (Throwable ex) {
Log.w(ex);
/*
java.lang.IllegalArgumentException: pointerIndex out of range
at android.view.MotionEvent.nativeGetAxisValue(MotionEvent.java:-2)
at android.view.MotionEvent.getX(MotionEvent.java:2379)
at androidx.viewpager.widget.ViewPager.onTouchEvent(SourceFile:2259)
at android.view.View.dispatchTouchEvent(View.java:14002)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3136)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2820)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3142)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2834)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:495)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1868)
at android.app.Activity.dispatchTouchEvent(Activity.java:4022)
at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(SourceFile:69)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:453)
at android.view.View.dispatchPointerEvent(View.java:14261)
*/
return false;
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
// Delegate to fragment first
if (super.onOptionsItemSelected(item))
return true;
performBack();
return true;
}
return super.onOptionsItemSelected(item);
}
public void performBack() {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Activity.java#3896
ActionBar ab = getSupportActionBar();
if (ab != null && ab.collapseActionView())
return;
FragmentManager fm = getSupportFragmentManager();
if (!fm.isStateSaved() && fm.popBackStackImmediate())
return;
}
finish();
}
@Override
public void onBackPressed() {
try {
super.onBackPressed();
} catch (Throwable ex) {
Log.w(ex);
/*
java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Handler android.app.FragmentHostCallback.getHandler()' on a null object reference
at android.app.FragmentManagerImpl.ensureExecReady(FragmentManager.java:2008)
at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2061)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:874)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:835)
at android.app.Activity.onBackPressed(Activity.java:3963)
at androidx.activity.ComponentActivity.access$001(Unknown)
at androidx.activity.ComponentActivity$1.run(SourceFile:1)
at androidx.activity.OnBackPressedDispatcher.onBackPressed(SourceFile:8)
at androidx.activity.f.run(Unknown:2)
at androidx.activity.g.onBackInvoked(Unknown:2)
at android.window.WindowOnBackInvokedDispatcher$OnBackInvokedCallbackWrapper.lambda$onBackInvoked$3$android-window-WindowOnBackInvokedDispatcher$OnBackInvokedCallbackWrapper(WindowOnBackInvokedDispatcher.java:267)
at android.window.WindowOnBackInvokedDispatcher$OnBackInvokedCallbackWrapper$$ExternalSyntheticLambda0.run(Unknown:2)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
*/
}
}
public void onBackPressedFragment() {
performBack();
}
@Override
public boolean shouldUpRecreateTask(Intent targetIntent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ComponentName cn = targetIntent.getComponent();
if (cn != null && BuildConfig.APPLICATION_ID.equals(cn.getPackageName()))
return false;
}
return super.shouldUpRecreateTask(targetIntent);
}
public boolean abShowing = true;
public ValueAnimator abAnimator = null;
public boolean isActionBarShown() {
return abShowing;
}
public void showActionBar(boolean show) {
ViewGroup abv = findViewById(R.id.action_bar);
if (abv == null)
return;
if (abShowing == show)
return;
abShowing = show;
int height = Helper.getActionBarHeight(this);
int current = abv.getLayoutParams().height;
int target = (show ? height : 0);
Log.i("ActionBar height=" + current + "..." + target);
if (abAnimator != null)
abAnimator.cancel();
abAnimator = ValueAnimator.ofInt(current, target);
abAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator anim) {
try {
Integer v = (Integer) anim.getAnimatedValue();
Log.i("ActionBar height=" + v);
ViewGroup.LayoutParams lparam = abv.getLayoutParams();
if (lparam.height == v)
Log.i("ActionBar ---");
else {
lparam.height = v;
abv.requestLayout();
}
} catch (Throwable ex) {
Log.e(ex);
}
}
});
abAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(@NonNull Animator animation) {
Log.i("ActionBar start");
}
@Override
public void onAnimationEnd(@NonNull Animator animation) {
Log.i("ActionBar end");
abAnimator = null;
}
@Override
public void onAnimationCancel(@NonNull Animator animation) {
Log.i("ActionBar cancel");
abAnimator = null;
}
@Override
public void onAnimationRepeat(@NonNull Animator animation) {
Log.i("ActionBar repeat");
}
});
abAnimator.setDuration(ACTIONBAR_ANIMATION_DURATION * Math.abs(current - target) / height);
abAnimator.start();
}
Handler getMainHandler() {
return ApplicationEx.getMainHandler();
}
private final FragmentManager.FragmentLifecycleCallbacks lifecycleCallbacks = new FragmentManager.FragmentLifecycleCallbacks() {
private long last = 0;
@Override
public void onFragmentPreAttached(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) {
log(fm, f, "onFragmentPreAttached");
}
@Override
public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) {
log(fm, f, "onFragmentAttached");
}
@Override
public void onFragmentPreCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
log(fm, f, "onFragmentPreCreated");
}
@Override
public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
log(fm, f, "onFragmentCreated");
}
@Override
public void onFragmentActivityCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
log(fm, f, "onFragmentActivityCreated");
}
@Override
public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull View v, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
log(fm, f, "onFragmentViewCreated");
}
@Override
public void onFragmentStarted(@NonNull FragmentManager fm, @NonNull Fragment f) {
log(fm, f, "onFragmentStarted");
}
@Override
public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {
log(fm, f, "onFragmentResumed");
}
@Override
public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {
log(fm, f, "onFragmentPaused");
}
@Override
public void onFragmentStopped(@NonNull FragmentManager fm, @NonNull Fragment f) {
log(fm, f, "onFragmentStopped");
}
@Override
public void onFragmentSaveInstanceState(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Bundle outState) {
log(fm, f, "onFragmentSaveInstanceState");
}
@Override
public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
log(fm, f, "onFragmentViewDestroyed");
Helper.clearViews(f);
}
@Override
public void onFragmentDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
log(fm, f, "onFragmentDestroyed");
}
@Override
public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {
log(fm, f, "onFragmentDetached");
}
private void log(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull String what) {
long start = last;
last = SystemClock.elapsedRealtime();
long elapsed = (start == 0 ? 0 : last - start);
Log.i(f.getClass().getSimpleName() + " " + what + " " + elapsed + " ms");
}
};
public interface IKeyPressedListener {
boolean onKeyPressed(KeyEvent event);
}
}