HomeActivity.java 87 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048
  1. /* _____ _
  2. * |_ _| |_ _ _ ___ ___ _ __ __ _
  3. * | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. * |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. *
  6. * Threema for Android
  7. * Copyright (c) 2013-2025 Threema GmbH
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. */
  21. package ch.threema.app.home;
  22. import android.annotation.SuppressLint;
  23. import android.app.Activity;
  24. import android.content.BroadcastReceiver;
  25. import android.content.Context;
  26. import android.content.Intent;
  27. import android.content.IntentFilter;
  28. import android.database.sqlite.SQLiteException;
  29. import android.graphics.Bitmap;
  30. import android.graphics.PorterDuff;
  31. import android.graphics.drawable.BitmapDrawable;
  32. import android.graphics.drawable.Drawable;
  33. import android.net.ConnectivityManager;
  34. import android.os.AsyncTask;
  35. import android.os.Build;
  36. import android.os.Bundle;
  37. import android.os.Handler;
  38. import android.text.SpannableString;
  39. import android.text.Spanned;
  40. import android.text.format.DateUtils;
  41. import android.text.style.TextAppearanceSpan;
  42. import android.view.Menu;
  43. import android.view.MenuItem;
  44. import android.view.View;
  45. import android.view.ViewGroup;
  46. import android.view.Window;
  47. import android.widget.ImageView;
  48. import android.widget.Toast;
  49. import com.bumptech.glide.Glide;
  50. import com.google.android.material.appbar.MaterialToolbar;
  51. import com.google.android.material.badge.BadgeDrawable;
  52. import com.google.android.material.badge.ExperimentalBadgeUtils;
  53. import com.google.android.material.bottomnavigation.BottomNavigationView;
  54. import org.slf4j.Logger;
  55. import java.io.File;
  56. import java.lang.ref.WeakReference;
  57. import java.security.MessageDigest;
  58. import java.util.Date;
  59. import java.util.LinkedList;
  60. import java.util.List;
  61. import java.util.Locale;
  62. import java.util.Objects;
  63. import java.util.Set;
  64. import java.util.concurrent.RejectedExecutionException;
  65. import java.util.stream.Collectors;
  66. import androidx.activity.result.ActivityResultLauncher;
  67. import androidx.activity.result.contract.ActivityResultContracts;
  68. import androidx.annotation.AnyThread;
  69. import androidx.annotation.ColorInt;
  70. import androidx.annotation.IdRes;
  71. import androidx.annotation.NonNull;
  72. import androidx.annotation.Nullable;
  73. import androidx.annotation.UiThread;
  74. import androidx.appcompat.app.ActionBar;
  75. import androidx.appcompat.widget.AppCompatImageView;
  76. import androidx.fragment.app.Fragment;
  77. import androidx.fragment.app.FragmentTransaction;
  78. import androidx.lifecycle.LifecycleOwner;
  79. import androidx.lifecycle.ViewModelProvider;
  80. import androidx.localbroadcastmanager.content.LocalBroadcastManager;
  81. import ch.threema.app.BuildFlavor;
  82. import ch.threema.app.R;
  83. import ch.threema.app.ThreemaApplication;
  84. import ch.threema.app.activities.BackupAdminActivity;
  85. import ch.threema.app.activities.BackupRestoreProgressActivity;
  86. import ch.threema.app.activities.ComposeMessageActivity;
  87. import ch.threema.app.activities.DirectoryActivity;
  88. import ch.threema.app.activities.DistributionListAddActivity;
  89. import ch.threema.app.activities.DownloadApkActivity;
  90. import ch.threema.app.activities.EnterSerialActivity;
  91. import ch.threema.app.activities.GroupAddActivity;
  92. import ch.threema.app.activities.HomeViewModel;
  93. import ch.threema.app.activities.ProblemSolverActivity;
  94. import ch.threema.app.activities.ServerMessageActivity;
  95. import ch.threema.app.activities.StarredMessagesActivity;
  96. import ch.threema.app.activities.ThreemaActivity;
  97. import ch.threema.app.activities.ThreemaAppCompatActivity;
  98. import ch.threema.app.activities.WhatsNewActivity;
  99. import ch.threema.app.activities.WorkIntroActivity;
  100. import ch.threema.app.activities.wizard.WizardBaseActivity;
  101. import ch.threema.app.activities.wizard.WizardStartActivity;
  102. import ch.threema.app.archive.ArchiveActivity;
  103. import ch.threema.app.asynctasks.AddContactRestrictionPolicy;
  104. import ch.threema.app.asynctasks.BasicAddOrUpdateContactBackgroundTask;
  105. import ch.threema.app.asynctasks.ContactAvailable;
  106. import ch.threema.app.asynctasks.ContactCreated;
  107. import ch.threema.app.asynctasks.ContactResult;
  108. import ch.threema.app.backuprestore.csv.BackupService;
  109. import ch.threema.app.backuprestore.csv.RestoreService;
  110. import ch.threema.app.dialogs.GenericAlertDialog;
  111. import ch.threema.app.dialogs.GenericProgressDialog;
  112. import ch.threema.app.dialogs.SMSVerificationDialog;
  113. import ch.threema.app.dialogs.ShowOnceDialog;
  114. import ch.threema.app.dialogs.SimpleStringAlertDialog;
  115. import ch.threema.app.fragments.ContactsSectionFragment;
  116. import ch.threema.app.fragments.MessageSectionFragment;
  117. import ch.threema.app.fragments.MyIDFragment;
  118. import ch.threema.app.glide.AvatarOptions;
  119. import ch.threema.app.globalsearch.GlobalSearchActivity;
  120. import ch.threema.app.grouplinks.OutgoingGroupRequestActivity;
  121. import ch.threema.app.listeners.AppIconListener;
  122. import ch.threema.app.listeners.ContactCountListener;
  123. import ch.threema.app.listeners.ConversationListener;
  124. import ch.threema.app.listeners.MessageListener;
  125. import ch.threema.app.listeners.ProfileListener;
  126. import ch.threema.app.listeners.SMSVerificationListener;
  127. import ch.threema.app.listeners.VoipCallListener;
  128. import ch.threema.app.managers.ListenerManager;
  129. import ch.threema.app.managers.ServiceManager;
  130. import ch.threema.app.messagereceiver.MessageReceiver;
  131. import ch.threema.app.multidevice.LinkedDevicesActivity;
  132. import ch.threema.app.preference.SettingsActivity;
  133. import ch.threema.app.push.PushService;
  134. import ch.threema.app.qrscanner.activity.BaseQrScannerActivity;
  135. import ch.threema.app.restrictions.AppRestrictionUtil;
  136. import ch.threema.app.routines.CheckLicenseRoutine;
  137. import ch.threema.app.services.ActivityService;
  138. import ch.threema.app.services.ContactService;
  139. import ch.threema.app.services.ContactServiceImpl;
  140. import ch.threema.app.services.ConversationService;
  141. import ch.threema.app.services.ConversationTagService;
  142. import ch.threema.app.services.DeviceService;
  143. import ch.threema.app.services.FileService;
  144. import ch.threema.app.services.LockAppService;
  145. import ch.threema.app.services.MessageService;
  146. import ch.threema.app.services.NotificationPreferenceService;
  147. import ch.threema.app.services.PassphraseService;
  148. import ch.threema.app.preference.service.PreferenceService;
  149. import ch.threema.app.services.ThreemaPushService;
  150. import ch.threema.app.services.UserService;
  151. import ch.threema.app.services.license.LicenseService;
  152. import ch.threema.app.services.notification.NotificationService;
  153. import ch.threema.app.tasks.ApplicationUpdateStepsTask;
  154. import ch.threema.app.threemasafe.ThreemaSafeMDMConfig;
  155. import ch.threema.app.threemasafe.ThreemaSafeService;
  156. import ch.threema.app.ui.IdentityPopup;
  157. import ch.threema.app.ui.InsetSides;
  158. import ch.threema.app.ui.OngoingCallNoticeMode;
  159. import ch.threema.app.ui.OngoingCallNoticeView;
  160. import ch.threema.app.ui.ViewExtensionsKt;
  161. import ch.threema.app.utils.AnimationUtil;
  162. import ch.threema.app.utils.ConfigUtils;
  163. import ch.threema.app.utils.ConnectionIndicatorUtil;
  164. import ch.threema.app.utils.DialogUtil;
  165. import ch.threema.app.utils.IntentDataUtil;
  166. import ch.threema.app.utils.LazyProperty;
  167. import ch.threema.app.utils.PowermanagerUtil;
  168. import ch.threema.app.utils.RuntimeUtil;
  169. import ch.threema.app.utils.TestUtil;
  170. import ch.threema.app.utils.executor.BackgroundExecutor;
  171. import ch.threema.app.voip.groupcall.GroupCallDescription;
  172. import ch.threema.app.voip.groupcall.GroupCallManager;
  173. import ch.threema.app.voip.groupcall.GroupCallObserver;
  174. import ch.threema.app.voip.groupcall.sfu.GroupCallController;
  175. import ch.threema.app.voip.services.VoipCallService;
  176. import ch.threema.app.webclient.activities.SessionsActivity;
  177. import ch.threema.app.webviews.SupportActivity;
  178. import ch.threema.base.utils.LoggingUtil;
  179. import ch.threema.data.repositories.ContactModelRepository;
  180. import ch.threema.domain.protocol.api.APIConnector;
  181. import ch.threema.domain.protocol.api.LinkMobileNoException;
  182. import ch.threema.domain.protocol.connection.ConnectionState;
  183. import ch.threema.domain.protocol.connection.ConnectionStateListener;
  184. import ch.threema.domain.protocol.connection.ServerConnection;
  185. import ch.threema.domain.taskmanager.TriggerSource;
  186. import ch.threema.localcrypto.MasterKey;
  187. import ch.threema.storage.DatabaseService;
  188. import ch.threema.storage.models.AbstractMessageModel;
  189. import ch.threema.storage.models.ContactModel;
  190. import ch.threema.storage.models.ConversationModel;
  191. import ch.threema.storage.models.ConversationTag;
  192. import ch.threema.storage.models.MessageState;
  193. import static ch.threema.app.startup.AppStartupUtilKt.finishAndRestartLaterIfNotReady;
  194. import static ch.threema.app.utils.ActiveScreenLoggerKt.logScreenVisibility;
  195. public class HomeActivity extends ThreemaAppCompatActivity implements
  196. SMSVerificationDialog.SMSVerificationDialogCallback,
  197. GenericAlertDialog.DialogClickListener,
  198. LifecycleOwner {
  199. private static final Logger logger = LoggingUtil.getThreemaLogger("HomeActivity");
  200. public static final String THREEMA_CHANNEL_IDENTITY = "*THREEMA";
  201. private static final String THREEMA_CHANNEL_INFO_COMMAND = "Info";
  202. private static final String THREEMA_CHANNEL_START_NEWS_COMMAND = "Start News";
  203. private static final String THREEMA_CHANNEL_START_ANDROID_COMMAND = "Start Android";
  204. private static final String THREEMA_CHANNEL_WORK_COMMAND = "Start Threema Work";
  205. private static final long PHONE_REQUEST_DELAY = 10 * DateUtils.MINUTE_IN_MILLIS;
  206. private static final String DIALOG_TAG_VERIFY_CODE = "vc";
  207. private static final String DIALOG_TAG_VERIFY_CODE_CONFIRM = "vcc";
  208. private static final String DIALOG_TAG_CANCEL_VERIFY = "cv";
  209. private static final String DIALOG_TAG_SERIAL_LOCKED = "sll";
  210. private static final String DIALOG_TAG_FINISH_UP = "fup";
  211. private static final String DIALOG_TAG_THREEMA_CHANNEL_VERIFY = "cvf";
  212. private static final String DIALOG_TAG_PASSWORD_PRESET_CONFIRM = "pwconf";
  213. private static final String FRAGMENT_TAG_MESSAGES = "0";
  214. private static final String FRAGMENT_TAG_CONTACTS = "1";
  215. private static final String FRAGMENT_TAG_PROFILE = "2";
  216. private static final String BUNDLE_CURRENT_FRAGMENT_TAG = "currentFragmentTag";
  217. private static final int REQUEST_CODE_WHATSNEW = 41912;
  218. public static final String EXTRA_SHOW_CONTACTS = "show_contacts";
  219. private @Nullable HomeViewModel viewModel = null;
  220. private ActionBar actionBar;
  221. private boolean isLicenseCheckStarted = false, isInitialized = false, isWhatsNewShown = false;
  222. private MaterialToolbar toolbar;
  223. private View connectionIndicator;
  224. private View noticeSMSLayout;
  225. private OngoingCallNoticeView ongoingCallNotice;
  226. private static long starredMessagesCount = 0L;
  227. private ServiceManager serviceManager;
  228. private NotificationService notificationService;
  229. private UserService userService;
  230. private ContactService contactService;
  231. private ContactModelRepository contactModelRepository;
  232. private APIConnector apiConnector;
  233. private LockAppService lockAppService;
  234. private PreferenceService preferenceService;
  235. private NotificationPreferenceService notificationPreferenceService;
  236. private ConversationService conversationService;
  237. private GroupCallManager groupCallManager;
  238. private @Nullable IdentityPopup identityPopup = null;
  239. @NonNull
  240. private final LazyProperty<BackgroundExecutor> backgroundExecutor = new LazyProperty<>(BackgroundExecutor::new);
  241. private enum UnsentMessageAction {
  242. ADD,
  243. REMOVE,
  244. }
  245. private final List<AbstractMessageModel> unsentMessages = new LinkedList<>();
  246. private final ActivityResultLauncher<String> notificationPermissionLauncher =
  247. registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
  248. if (!Boolean.TRUE.equals(isGranted)) {
  249. logger.warn("Notification permission was not granted");
  250. }
  251. });
  252. private final ActivityResultLauncher<Intent> problemSolverLauncher =
  253. registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> updateWarningButton());
  254. private BroadcastReceiver checkLicenseBroadcastReceiver = null;
  255. private final BroadcastReceiver currentCheckAppReceiver = new BroadcastReceiver() {
  256. @Override
  257. public void onReceive(Context context, final Intent intent) {
  258. RuntimeUtil.runOnUiThread(() -> {
  259. if (IntentDataUtil.ACTION_LICENSE_NOT_ALLOWED.equals(intent.getAction())) {
  260. BuildFlavor.LicenseType currentLicenseType = BuildFlavor.getCurrent().getLicenseType();
  261. if (currentLicenseType == BuildFlavor.LicenseType.SERIAL ||
  262. currentLicenseType == BuildFlavor.LicenseType.GOOGLE_WORK ||
  263. currentLicenseType == BuildFlavor.LicenseType.HMS_WORK ||
  264. currentLicenseType == BuildFlavor.LicenseType.ONPREM) {
  265. //show enter serial stuff
  266. startActivityForResult(new Intent(HomeActivity.this, EnterSerialActivity.class), ThreemaActivity.ACTIVITY_ID_ENTER_SERIAL);
  267. } else {
  268. showErrorTextAndExit(IntentDataUtil.getMessage(intent));
  269. }
  270. } else if (IntentDataUtil.ACTION_UPDATE_AVAILABLE.equals(intent.getAction()) && BuildFlavor.getCurrent().getMaySelfUpdate() && userService != null && userService.hasIdentity()) {
  271. logger.info("App update available. Opening DownloadApkActivity.");
  272. new Handler().postDelayed(() -> {
  273. Intent dialogIntent = new Intent(intent);
  274. dialogIntent.setClass(HomeActivity.this, DownloadApkActivity.class);
  275. startActivity(dialogIntent);
  276. }, DateUtils.SECOND_IN_MILLIS * 5);
  277. }
  278. });
  279. }
  280. };
  281. private BottomNavigationView bottomNavigationView;
  282. private View mainContent, toolbarWarningButton;
  283. private String currentFragmentTag;
  284. private static class UpdateBottomNavigationBadgeTask extends AsyncTask<Void, Void, Integer> {
  285. private ConversationTagService conversationTagService = null;
  286. private final WeakReference<Activity> activityWeakReference;
  287. UpdateBottomNavigationBadgeTask(Activity activity) {
  288. activityWeakReference = new WeakReference<>(activity);
  289. try {
  290. conversationTagService = Objects.requireNonNull(ThreemaApplication.getServiceManager()).getConversationTagService();
  291. } catch (Exception e) {
  292. logger.error("UpdateBottomNav", e);
  293. }
  294. }
  295. @Override
  296. protected Integer doInBackground(Void... voids) {
  297. ConversationService conversationService;
  298. try {
  299. conversationService = ThreemaApplication.getServiceManager().getConversationService();
  300. } catch (Exception e) {
  301. return 0;
  302. }
  303. if (conversationService == null) {
  304. return 0;
  305. }
  306. List<ConversationModel> conversationModels = conversationService.getAll(false, new ConversationService.Filter() {
  307. @Override
  308. public boolean onlyUnread() {
  309. return true;
  310. }
  311. });
  312. int unread = 0;
  313. for (ConversationModel conversationModel : conversationModels) {
  314. unread += conversationModel.getUnreadCount();
  315. }
  316. if (conversationTagService != null) {
  317. // First check whether there are some conversations that are marked as unread. This
  318. // check is expected to be fast, as usually there are not many chats that are marked
  319. // as unread.
  320. if (conversationTagService.getCount(ConversationTag.MARKED_AS_UNREAD) > 0) {
  321. // In case there is at least one unread tag, we create a set of all possible
  322. // conversation uids to efficiently check that the unread tags are valid.
  323. Set<String> shownConversationUids = conversationService.getAll(false)
  324. .stream()
  325. .map(ConversationModel::getUid)
  326. .collect(Collectors.toSet());
  327. List<String> unreadUids = conversationTagService.getConversationUidsByTag(ConversationTag.MARKED_AS_UNREAD);
  328. for (String unreadUid : unreadUids) {
  329. if (shownConversationUids.contains(unreadUid)) {
  330. unread++;
  331. } else {
  332. logger.warn("Conversation '{}' is marked as unread but not shown. Deleting the unread flag.", unreadUid);
  333. conversationTagService.removeTag(unreadUid, ConversationTag.MARKED_AS_UNREAD, TriggerSource.LOCAL);
  334. }
  335. }
  336. }
  337. }
  338. return unread;
  339. }
  340. @Override
  341. protected void onPostExecute(Integer count) {
  342. if (activityWeakReference.get() != null) {
  343. BottomNavigationView bottomNavigationView = activityWeakReference.get().findViewById(R.id.bottom_navigation);
  344. if (bottomNavigationView != null) {
  345. BadgeDrawable badgeDrawable = bottomNavigationView.getOrCreateBadge(R.id.messages);
  346. if (badgeDrawable.getVerticalOffset() == 0) {
  347. badgeDrawable.setVerticalOffset(activityWeakReference.get().getResources().getDimensionPixelSize(R.dimen.bottom_nav_badge_offset_vertical));
  348. }
  349. badgeDrawable.setNumber(count);
  350. badgeDrawable.setVisible(count > 0);
  351. }
  352. }
  353. }
  354. }
  355. private static class UpdateStarredMessagesTask extends AsyncTask<Void, Void, Long> {
  356. @Override
  357. protected Long doInBackground(Void... voids) {
  358. MessageService messageService;
  359. try {
  360. ServiceManager serviceManager = ThreemaApplication.getServiceManager();
  361. if (serviceManager != null) {
  362. messageService = serviceManager.getMessageService();
  363. return messageService.countStarredMessages();
  364. } else {
  365. if (logger != null) {
  366. logger.warn("Could not count starred messages because service manager is null");
  367. }
  368. return 0L;
  369. }
  370. } catch (Exception e) {
  371. if (logger != null) {
  372. logger.error("Unable to count starred messages", e);
  373. }
  374. return 0L;
  375. }
  376. }
  377. @Override
  378. protected void onPostExecute(Long count) {
  379. starredMessagesCount = count;
  380. }
  381. }
  382. private final ConnectionStateListener connectionStateListener = this::updateConnectionIndicator;
  383. private void updateUnsentMessagesList(@NonNull AbstractMessageModel modifiedMessageModel, UnsentMessageAction action) {
  384. int numCurrentUnsent = unsentMessages.size();
  385. synchronized (unsentMessages) {
  386. String uid = modifiedMessageModel.getUid();
  387. // Check whether the message model with the same uid is already in the list or not
  388. AbstractMessageModel containedMessageModel = null;
  389. for (AbstractMessageModel unsentMessage : unsentMessages) {
  390. if (TestUtil.compare(unsentMessage.getUid(), uid)) {
  391. containedMessageModel = unsentMessage;
  392. break;
  393. }
  394. }
  395. switch (action) {
  396. case ADD:
  397. // Only add the message model if it is not yet in the list
  398. if (containedMessageModel == null) {
  399. unsentMessages.add(modifiedMessageModel);
  400. }
  401. break;
  402. case REMOVE:
  403. // Remove message model if it is in the list
  404. if (containedMessageModel != null) {
  405. unsentMessages.remove(containedMessageModel);
  406. }
  407. break;
  408. }
  409. int numNewUnsent = unsentMessages.size();
  410. // Update the notification if there was a change
  411. if (notificationService != null && numCurrentUnsent != numNewUnsent) {
  412. notificationService.showUnsentMessageNotification(unsentMessages);
  413. }
  414. }
  415. }
  416. private final SMSVerificationListener smsVerificationListener = new SMSVerificationListener() {
  417. @Override
  418. public void onVerified() {
  419. RuntimeUtil.runOnUiThread(() -> {
  420. if (noticeSMSLayout != null) {
  421. AnimationUtil.collapse(noticeSMSLayout, null, true);
  422. }
  423. });
  424. }
  425. @Override
  426. public void onVerificationStarted() {
  427. RuntimeUtil.runOnUiThread(() -> {
  428. if (noticeSMSLayout != null) {
  429. AnimationUtil.expand(noticeSMSLayout, null, true);
  430. }
  431. });
  432. }
  433. };
  434. private void updateBottomNavigation() {
  435. if (preferenceService.getShowUnreadBadge()) {
  436. RuntimeUtil.runOnUiThread(() -> {
  437. try {
  438. new UpdateBottomNavigationBadgeTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  439. } catch (RejectedExecutionException e) {
  440. try {
  441. new UpdateBottomNavigationBadgeTask(this).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
  442. } catch (RejectedExecutionException ignored) {
  443. }
  444. }
  445. });
  446. }
  447. }
  448. private final ConversationListener conversationListener = new ConversationListener() {
  449. @Override
  450. public void onNew(ConversationModel conversationModel) {
  451. updateBottomNavigation();
  452. }
  453. @Override
  454. public void onModified(ConversationModel modifiedConversationModel, Integer oldPosition) {
  455. updateBottomNavigation();
  456. }
  457. @Override
  458. public void onRemoved(ConversationModel conversationModel) {
  459. updateBottomNavigation();
  460. }
  461. @Override
  462. public void onModifiedAll() {
  463. updateBottomNavigation();
  464. }
  465. };
  466. private final MessageListener messageListener = new MessageListener() {
  467. @Override
  468. public void onNew(AbstractMessageModel newMessage) {
  469. //do nothing
  470. }
  471. @Override
  472. public void onModified(@NonNull List<AbstractMessageModel> modifiedMessageModels) {
  473. for (AbstractMessageModel modifiedMessageModel : modifiedMessageModels) {
  474. if (!modifiedMessageModel.isStatusMessage()
  475. && modifiedMessageModel.isOutbox()) {
  476. if (modifiedMessageModel.getState() == MessageState.SENDFAILED) {
  477. updateUnsentMessagesList(modifiedMessageModel, UnsentMessageAction.ADD);
  478. } else {
  479. updateUnsentMessagesList(modifiedMessageModel, UnsentMessageAction.REMOVE);
  480. }
  481. }
  482. }
  483. }
  484. @Override
  485. public void onRemoved(AbstractMessageModel removedMessageModel) {
  486. updateUnsentMessagesList(removedMessageModel, UnsentMessageAction.REMOVE);
  487. }
  488. @Override
  489. public void onRemoved(List<AbstractMessageModel> removedMessageModels) {
  490. for (AbstractMessageModel removedMessageModel : removedMessageModels) {
  491. updateUnsentMessagesList(removedMessageModel, UnsentMessageAction.REMOVE);
  492. }
  493. }
  494. @Override
  495. public void onProgressChanged(AbstractMessageModel messageModel, int newProgress) {
  496. //do nothing
  497. }
  498. @Override
  499. public void onResendDismissed(@NonNull AbstractMessageModel messageModel) {
  500. updateUnsentMessagesList(messageModel, UnsentMessageAction.REMOVE);
  501. }
  502. };
  503. private final AppIconListener appIconListener = () -> RuntimeUtil.runOnUiThread(this::updateAppLogo);
  504. private final ProfileListener profileListener = new ProfileListener() {
  505. @Override
  506. public void onAvatarChanged(@NonNull TriggerSource triggerSource) {
  507. RuntimeUtil.runOnUiThread(() -> updateDrawerImage());
  508. }
  509. @Override
  510. public void onNicknameChanged(String newNickname) {
  511. }
  512. };
  513. private final VoipCallListener voipCallListener = new VoipCallListener() {
  514. @Override
  515. public void onStart(String contact, long elpasedTimeMs) {
  516. updateOngoingCallNotice();
  517. }
  518. @Override
  519. public void onEnd() {
  520. hideOngoingVoipCallNotice();
  521. }
  522. };
  523. private final GroupCallObserver groupCallObserver = call -> updateOngoingCallNotice();
  524. private final ContactCountListener contactCountListener = new ContactCountListener() {
  525. @Override
  526. public void onNewContactsCountUpdated(int last24hoursCount) {
  527. if (preferenceService != null && preferenceService.getShowUnreadBadge()) {
  528. RuntimeUtil.runOnUiThread(() -> {
  529. if (!isFinishing() && !isDestroyed() && !isChangingConfigurations()) {
  530. BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
  531. if (bottomNavigationView != null) {
  532. BadgeDrawable badgeDrawable = bottomNavigationView.getOrCreateBadge(R.id.contacts);
  533. // the contacts tab item badge uses custom colors (normally badges are red)
  534. badgeDrawable.setBackgroundColor(
  535. getContactsTabBadgeColor(bottomNavigationView.getSelectedItemId())
  536. );
  537. if (badgeDrawable.getVerticalOffset() == 0) {
  538. badgeDrawable.setVerticalOffset(getResources().getDimensionPixelSize(R.dimen.bottom_nav_badge_offset_vertical));
  539. }
  540. badgeDrawable.setVisible(last24hoursCount > 0);
  541. }
  542. }
  543. });
  544. }
  545. }
  546. };
  547. @Override
  548. @ExperimentalBadgeUtils
  549. protected void onCreate(Bundle savedInstanceState) {
  550. logger.info("onCreate");
  551. final boolean isColdStart = savedInstanceState == null;
  552. super.onCreate(savedInstanceState);
  553. logScreenVisibility(this, logger);
  554. if (finishAndRestartLaterIfNotReady(this)) {
  555. return;
  556. }
  557. if (BackupService.isRunning() || RestoreService.isRunning()) {
  558. startActivity(new Intent(this, BackupRestoreProgressActivity.class));
  559. finish();
  560. return;
  561. }
  562. if (ConfigUtils.isSerialLicensed() && !ConfigUtils.isSerialLicenseValid()) {
  563. if (shouldShowWorkIntroScreen()) {
  564. startActivity(new Intent(this, WorkIntroActivity.class));
  565. } else {
  566. startActivityForResult(new Intent(this, EnterSerialActivity.class), ThreemaActivity.ACTIVITY_ID_ENTER_SERIAL);
  567. }
  568. finish();
  569. } else {
  570. this.startHomeActivity(savedInstanceState);
  571. // only execute this on first startup
  572. if (isColdStart) {
  573. if (preferenceService != null && userService != null && userService.hasIdentity()) {
  574. if (ConfigUtils.isWorkRestricted()) {
  575. // update configuration
  576. final ThreemaSafeMDMConfig newConfig = ThreemaSafeMDMConfig.getInstance();
  577. ThreemaSafeService threemaSafeService = getThreemaSafeService();
  578. if (threemaSafeService != null) {
  579. if (newConfig.hasChanged(preferenceService)) {
  580. if (newConfig.isBackupForced()) {
  581. if (newConfig.isSkipBackupPasswordEntry()) {
  582. // enable with given password
  583. byte[] newMasterKey = threemaSafeService.deriveMasterKey(newConfig.getPassword(), newConfig.getIdentity());
  584. byte[] oldMasterKey = preferenceService.getThreemaSafeMasterKey();
  585. // show warning dialog only when password was changed
  586. if (MessageDigest.isEqual(newMasterKey, oldMasterKey)) {
  587. reconfigureSafe(threemaSafeService, newConfig);
  588. enableSafe(threemaSafeService, newConfig, null);
  589. } else {
  590. GenericAlertDialog dialog = GenericAlertDialog.newInstance(R.string.threema_safe, R.string.safe_managed_new_password_confirm, R.string.accept, R.string.real_not_now);
  591. dialog.setData(newConfig);
  592. dialog.show(getSupportFragmentManager(), DIALOG_TAG_PASSWORD_PRESET_CONFIRM);
  593. }
  594. } else if (threemaSafeService.getThreemaSafeMasterKey() != null && threemaSafeService.getThreemaSafeMasterKey().length > 0) {
  595. // no password has been given by admin but a master key from a previous backup exists
  596. // -> create a new backup with existing password
  597. reconfigureSafe(threemaSafeService, newConfig);
  598. enableSafe(threemaSafeService, newConfig, threemaSafeService.getThreemaSafeMasterKey());
  599. } else {
  600. reconfigureSafe(threemaSafeService, newConfig);
  601. threemaSafeService.launchForcedPasswordDialog(this, true);
  602. finish();
  603. return;
  604. }
  605. } else {
  606. reconfigureSafe(threemaSafeService, newConfig);
  607. }
  608. } else {
  609. if (newConfig.isBackupForced() && !preferenceService.getThreemaSafeEnabled()) {
  610. // config has not changed but safe is still not enabled. fix it.
  611. if (newConfig.isSkipBackupPasswordEntry()) {
  612. // enable with given password
  613. enableSafe(threemaSafeService, newConfig, null);
  614. } else {
  615. // ask user for a new password
  616. threemaSafeService.launchForcedPasswordDialog(this, true);
  617. finish();
  618. return;
  619. }
  620. }
  621. }
  622. // save current config as new reference
  623. newConfig.saveConfig(preferenceService);
  624. }
  625. }
  626. }
  627. }
  628. }
  629. viewModel = new ViewModelProvider(this).get(HomeViewModel.class);
  630. }
  631. private boolean hasIdentity() {
  632. try {
  633. return ThreemaApplication.requireServiceManager().getUserService().hasIdentity();
  634. } catch (NullPointerException npe) {
  635. logger.error("user service not available");
  636. return false;
  637. }
  638. }
  639. /**
  640. * Check whether the work intro screen should be shown. The work intro screen contains a notice
  641. * to direct users to the private version of the app in case it is possible that they installed
  642. * the work or onprem app by mistake. In certain cases we can rule out an oversight and skip the
  643. * screen.
  644. *
  645. * @return true if the screen should be shown
  646. */
  647. private boolean shouldShowWorkIntroScreen() {
  648. // Don't show the screen if the app is already set up with an identity.
  649. if (ThreemaApplication.requireServiceManager().getUserService().hasIdentity()) {
  650. return false;
  651. }
  652. // If it is no work (including onprem) build, we should not show the screen.
  653. if (!ConfigUtils.isWorkBuild()) {
  654. return false;
  655. }
  656. // If the app is restricted, then we should skip the screen as it is very likely intended to
  657. // use the work or onprem app.
  658. if (ConfigUtils.isWorkRestricted()) {
  659. return false;
  660. }
  661. // If the app is not installed from a store, the chance that the private app should be used
  662. // instead is smaller and therefore we should skip the screen.
  663. if (!ConfigUtils.isInstalledFromStore(this)) {
  664. return false;
  665. }
  666. // On whitelabel onprem build it doesn't make sense to show the work intro screen.
  667. return !ConfigUtils.isWhitelabelOnPremBuild(this);
  668. }
  669. private void reconfigureSafe(@NonNull ThreemaSafeService threemaSafeService, @NonNull ThreemaSafeMDMConfig newConfig) {
  670. // dispose of old backup, if any
  671. try {
  672. threemaSafeService.deleteBackup();
  673. threemaSafeService.setEnabled(false);
  674. } catch (Exception e) {
  675. // ignore
  676. }
  677. if (preferenceService != null) {
  678. preferenceService.setThreemaSafeServerInfo(newConfig.getServerInfo());
  679. }
  680. }
  681. private ThreemaSafeService getThreemaSafeService() {
  682. try {
  683. return serviceManager.getThreemaSafeService();
  684. } catch (Exception e) {
  685. //
  686. }
  687. return null;
  688. }
  689. @Override
  690. protected void onStart() {
  691. super.onStart();
  692. logger.info("HomeActivity started");
  693. if (serviceManager != null) {
  694. // Check if there are any server messages to display
  695. DatabaseService databaseService = serviceManager.getDatabaseService();
  696. try {
  697. if (databaseService.getServerMessageModelFactory().count() > 0) {
  698. Intent intent = new Intent(this, ServerMessageActivity.class);
  699. startActivity(intent);
  700. }
  701. } catch (SQLiteException e) {
  702. logger.error("Could not get server message model count", e);
  703. }
  704. if (viewModel != null) {
  705. viewModel.checkMultiDeviceGroup(serviceManager);
  706. }
  707. }
  708. }
  709. @UiThread
  710. private void showMainContent() {
  711. if (mainContent != null) {
  712. if (mainContent.getVisibility() != View.VISIBLE) {
  713. mainContent.setVisibility(View.VISIBLE);
  714. }
  715. }
  716. }
  717. private void updateWarningButton() {
  718. logger.debug("updateWarningButton");
  719. if (toolbarWarningButton != null) {
  720. toolbarWarningButton.setVisibility(shouldShowToolbarWarning() ? View.VISIBLE : View.GONE);
  721. }
  722. }
  723. private boolean shouldShowToolbarWarning() {
  724. var appContext = ThreemaApplication.getAppContext();
  725. return
  726. ConfigUtils.isBackgroundRestricted(appContext) ||
  727. ConfigUtils.isBackgroundDataRestricted(appContext, false) ||
  728. ConfigUtils.isNotificationsDisabled(appContext) ||
  729. (preferenceService.isVoipEnabled() && ConfigUtils.isFullScreenNotificationsDisabled(appContext)) ||
  730. ((preferenceService.useThreemaPush() || BuildFlavor.getCurrent().getForceThreemaPush()) && !PowermanagerUtil.isIgnoringBatteryOptimizations(appContext)) ||
  731. (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && preferenceService.shouldShowUnsupportedAndroidVersionWarning());
  732. }
  733. private void showWhatsNew() {
  734. final boolean skipWhatsNew = true; // set this to false if you want to show a What's New screen
  735. if (preferenceService != null) {
  736. if (!preferenceService.isLatestVersion(this)) {
  737. // so the app has just been updated
  738. ConfigUtils.requestNotificationPermission(this, notificationPermissionLauncher, preferenceService);
  739. if (preferenceService.getPrivacyPolicyAccepted() == null) {
  740. preferenceService.setPrivacyPolicyAccepted(new Date(), PreferenceService.PRIVACY_POLICY_ACCEPT_UPDATE);
  741. }
  742. if (!ConfigUtils.isWorkBuild() && !RuntimeUtil.isInTest() && !isFinishing()) {
  743. if (skipWhatsNew) {
  744. isWhatsNewShown = false; // make sure isWhatsNewShown is set to false here if whatsnew is skipped - otherwise pin unlock will not be shown once
  745. } else {
  746. int previous = preferenceService.getLatestVersion() % 10000;
  747. // To not show the same dialog twice, it is only shown if the previous version
  748. // is prior to the first version that used this dialog.
  749. // Use the version code of the first version where this dialog should be shown.
  750. if (previous < 1069) {
  751. isWhatsNewShown = true;
  752. Intent intent = new Intent(this, WhatsNewActivity.class);
  753. startActivityForResult(intent, REQUEST_CODE_WHATSNEW);
  754. overridePendingTransition(R.anim.abc_fade_in, R.anim.abc_fade_out);
  755. }
  756. }
  757. }
  758. preferenceService.setLatestVersion(this);
  759. }
  760. }
  761. }
  762. @SuppressLint("StaticFieldLeak")
  763. private void enableSafe(@NonNull ThreemaSafeService threemaSafeService, ThreemaSafeMDMConfig mdmConfig, final byte[] masterkeyPreset) {
  764. new AsyncTask<Void, Void, byte[]>() {
  765. @Override
  766. protected byte[] doInBackground(Void... voids) {
  767. if (masterkeyPreset == null) {
  768. return threemaSafeService.deriveMasterKey(mdmConfig.getPassword(), userService.getIdentity());
  769. }
  770. return masterkeyPreset;
  771. }
  772. @Override
  773. protected void onPostExecute(byte[] masterkey) {
  774. if (masterkey != null) {
  775. threemaSafeService.storeMasterKey(masterkey);
  776. preferenceService.setThreemaSafeServerInfo(mdmConfig.getServerInfo());
  777. threemaSafeService.setEnabled(true);
  778. threemaSafeService.uploadNow(true);
  779. } else {
  780. Toast.makeText(HomeActivity.this, R.string.safe_error_preparing, Toast.LENGTH_LONG).show();
  781. }
  782. }
  783. }.execute();
  784. }
  785. private void showQRPopup() {
  786. int[] location = getMiniAvatarLocation();
  787. identityPopup = new IdentityPopup(this);
  788. identityPopup.show(
  789. this,
  790. toolbar,
  791. location,
  792. () -> {
  793. // show profile fragment
  794. bottomNavigationView.post(() -> bottomNavigationView.findViewById(R.id.my_profile).performClick());
  795. },
  796. () -> identityPopup = null
  797. );
  798. }
  799. private int[] getMiniAvatarLocation() {
  800. int[] location = new int[2];
  801. toolbar.getLocationInWindow(location);
  802. location[0] += toolbar.getContentInsetLeft() +
  803. ((getResources().getDimensionPixelSize(R.dimen.navigation_icon_padding) +
  804. getResources().getDimensionPixelSize(R.dimen.navigation_icon_size)) / 2);
  805. location[1] += toolbar.getHeight() / 2;
  806. return location;
  807. }
  808. private void checkApp() {
  809. try {
  810. if (this.currentCheckAppReceiver != null) {
  811. LocalBroadcastManager.getInstance(this).unregisterReceiver(this.currentCheckAppReceiver);
  812. }
  813. if (this.checkLicenseBroadcastReceiver != null) {
  814. this.unregisterReceiver(this.checkLicenseBroadcastReceiver);
  815. }
  816. } catch (IllegalArgumentException r) {
  817. //not registered... ignore exceptions
  818. }
  819. //Register not licensed and update available broadcast
  820. IntentFilter filter = new IntentFilter();
  821. filter.addAction(IntentDataUtil.ACTION_LICENSE_NOT_ALLOWED);
  822. filter.addAction(IntentDataUtil.ACTION_UPDATE_AVAILABLE);
  823. LocalBroadcastManager.getInstance(this).registerReceiver(currentCheckAppReceiver, filter);
  824. this.checkLicense();
  825. }
  826. private void checkLicense() {
  827. if (this.isLicenseCheckStarted) {
  828. return;
  829. }
  830. if (serviceManager != null) {
  831. DeviceService deviceService = serviceManager.getDeviceService();
  832. if (deviceService.isOnline()) {
  833. //start check directly
  834. CheckLicenseRoutine check;
  835. check = new CheckLicenseRoutine(
  836. this,
  837. serviceManager.getAPIConnector(),
  838. serviceManager.getUserService(),
  839. deviceService,
  840. serviceManager.getLicenseService(),
  841. serviceManager.getIdentityStore()
  842. );
  843. RuntimeUtil.runOnWorkerThread(check);
  844. this.isLicenseCheckStarted = true;
  845. if (this.checkLicenseBroadcastReceiver != null) {
  846. try {
  847. this.unregisterReceiver(this.checkLicenseBroadcastReceiver);
  848. } catch (IllegalArgumentException e) {
  849. logger.error("Exception", e);
  850. }
  851. }
  852. } else {
  853. if (this.checkLicenseBroadcastReceiver == null) {
  854. this.checkLicenseBroadcastReceiver = new BroadcastReceiver() {
  855. @Override
  856. public void onReceive(Context context, Intent intent) {
  857. logger.debug("receive connectivity change in main activity to check license");
  858. checkLicense();
  859. }
  860. };
  861. this.registerReceiver(
  862. this.checkLicenseBroadcastReceiver,
  863. new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
  864. );
  865. }
  866. }
  867. }
  868. }
  869. @Override
  870. protected void onDestroy() {
  871. logger.info("onDestroy");
  872. ActivityService.activityDestroyed(this);
  873. if (identityPopup != null) {
  874. identityPopup.dismiss();
  875. }
  876. try {
  877. if (this.currentCheckAppReceiver != null) {
  878. LocalBroadcastManager.getInstance(this).unregisterReceiver(this.currentCheckAppReceiver);
  879. }
  880. if (this.checkLicenseBroadcastReceiver != null) {
  881. this.unregisterReceiver(this.checkLicenseBroadcastReceiver);
  882. }
  883. } catch (IllegalArgumentException r) {
  884. //not registered... ignore exceptions
  885. }
  886. // remove listeners to avoid memory leaks
  887. ListenerManager.messageListeners.remove(this.messageListener);
  888. ListenerManager.smsVerificationListeners.remove(this.smsVerificationListener);
  889. ListenerManager.appIconListeners.remove(this.appIconListener);
  890. ListenerManager.profileListeners.remove(this.profileListener);
  891. ListenerManager.voipCallListeners.remove(this.voipCallListener);
  892. if (groupCallManager != null) {
  893. groupCallManager.removeGeneralGroupCallObserver(groupCallObserver);
  894. }
  895. ListenerManager.conversationListeners.remove(this.conversationListener);
  896. ListenerManager.contactCountListener.remove(this.contactCountListener);
  897. if (serviceManager != null) {
  898. serviceManager.getConnection().removeConnectionStateListener(connectionStateListener);
  899. }
  900. super.onDestroy();
  901. }
  902. private void showErrorTextAndExit(String text) {
  903. GenericAlertDialog.newInstance(R.string.error, text, R.string.finish, 0)
  904. .show(getSupportFragmentManager(), DIALOG_TAG_FINISH_UP);
  905. }
  906. private void startHomeActivity(Bundle savedInstanceState) {
  907. // at this point the app should be unlocked, licensed and updated
  908. this.serviceManager = ThreemaApplication.requireServiceManager();
  909. if (this.isInitialized) {
  910. return;
  911. }
  912. boolean isAppStart = savedInstanceState == null;
  913. this.userService = this.serviceManager.getUserService();
  914. this.preferenceService = this.serviceManager.getPreferenceService();
  915. this.notificationPreferenceService = serviceManager.getNotificationPreferenceService();
  916. this.notificationService = serviceManager.getNotificationService();
  917. this.lockAppService = serviceManager.getLockAppService();
  918. try {
  919. this.conversationService = serviceManager.getConversationService();
  920. this.contactService = serviceManager.getContactService();
  921. this.groupCallManager = serviceManager.getGroupCallManager();
  922. } catch (Exception e) {
  923. //
  924. }
  925. this.contactModelRepository = serviceManager.getModelRepositories().getContacts();
  926. this.apiConnector = serviceManager.getAPIConnector();
  927. if (preferenceService == null || notificationService == null || userService == null) {
  928. finish();
  929. return;
  930. }
  931. // TODO(ANDR-2816): Remove
  932. preferenceService.removeLastNotificationRationaleShown();
  933. // reset connectivity status
  934. preferenceService.setLastOnlineStatus(serviceManager.getDeviceService().isOnline());
  935. // remove restart notification
  936. notificationService.cancelRestartNotification();
  937. ListenerManager.smsVerificationListeners.add(this.smsVerificationListener);
  938. ListenerManager.messageListeners.add(this.messageListener);
  939. ListenerManager.appIconListeners.add(this.appIconListener);
  940. ListenerManager.profileListeners.add(this.profileListener);
  941. ListenerManager.voipCallListeners.add(this.voipCallListener);
  942. if (groupCallManager != null) {
  943. groupCallManager.addGeneralGroupCallObserver(groupCallObserver);
  944. }
  945. ListenerManager.conversationListeners.add(this.conversationListener);
  946. ListenerManager.contactCountListener.add(this.contactCountListener);
  947. initHomeActivity(savedInstanceState);
  948. if (isAppStart) {
  949. if (preferenceService.checkForAppUpdate(this)) {
  950. serviceManager.getTaskManager().schedule(new ApplicationUpdateStepsTask(serviceManager));
  951. }
  952. }
  953. }
  954. @UiThread
  955. private void initHomeActivity(@Nullable Bundle savedInstanceState) {
  956. final boolean isAppStart = savedInstanceState == null;
  957. // licensing
  958. checkApp();
  959. // start wizard if necessary
  960. if (notificationPreferenceService.getWizardRunning() || !userService.hasIdentity()) {
  961. logger.debug("Missing identity. Wizard running: {}", notificationPreferenceService.getWizardRunning());
  962. if (userService.hasIdentity()) {
  963. startActivity(new Intent(this, WizardBaseActivity.class));
  964. } else {
  965. startActivity(new Intent(this, WizardStartActivity.class));
  966. }
  967. finish();
  968. return;
  969. }
  970. // set up content
  971. setContentView(R.layout.activity_home);
  972. // Wizard complete
  973. // Write master key now if no passphrase has been set
  974. if (!ThreemaApplication.getMasterKey().isProtected()) {
  975. try {
  976. ThreemaApplication.getMasterKey().setPassphrase(null);
  977. } catch (Exception e) {
  978. // better die if something went wrong as the master key may not have been saved
  979. throw new RuntimeException(e);
  980. }
  981. }
  982. // Set up the action bar.
  983. initActionBar();
  984. //init custom icon
  985. updateAppLogo();
  986. actionBar.setDisplayShowTitleEnabled(false);
  987. actionBar.setDisplayUseLogoEnabled(false);
  988. ConfigUtils.setScreenshotsAllowed(this, preferenceService, lockAppService);
  989. // add connection state listener for displaying colored connection status line above toolbar
  990. RuntimeUtil.runOnWorkerThread(() -> {
  991. if (serviceManager != null) {
  992. ServerConnection connection = serviceManager.getConnection();
  993. connection.addConnectionStateListener(connectionStateListener);
  994. updateConnectionIndicator(connection.getConnectionState());
  995. }
  996. });
  997. // call onPrepareOptionsMenu
  998. this.invalidateOptionsMenu();
  999. // Checks on app start
  1000. if (isAppStart) {
  1001. if (BuildFlavor.getCurrent().getForceThreemaPush()) {
  1002. preferenceService.setUseThreemaPush(true);
  1003. } else if (!preferenceService.useThreemaPush() && !PushService.servicesInstalled(this)) {
  1004. // If a non-libre build of Threema cannot find push services, fall back to Threema Push
  1005. this.enableThreemaPush();
  1006. if (!ConfigUtils.isAmazonDevice() && !ConfigUtils.isWorkBuild()) {
  1007. RuntimeUtil.runOnUiThread(() -> {
  1008. // Show "push not available" dialog
  1009. int title = R.string.push_not_available_title;
  1010. final String message = getString(R.string.push_not_available_text1) + "\n\n" + getString(R.string.push_not_available_text2, getString(R.string.app_name));
  1011. ShowOnceDialog.newInstance(title, message).show(getSupportFragmentManager(), "nopush");
  1012. });
  1013. }
  1014. }
  1015. }
  1016. this.mainContent = findViewById(R.id.main_content);
  1017. this.toolbarWarningButton = findViewById(R.id.toolbar_warning);
  1018. this.toolbarWarningButton.setOnClickListener(v -> {
  1019. Intent intent = new Intent(HomeActivity.this, ProblemSolverActivity.class);
  1020. problemSolverLauncher.launch(intent);
  1021. });
  1022. this.noticeSMSLayout = findViewById(R.id.notice_sms_layout);
  1023. findViewById(R.id.notice_sms_button_enter_code).setOnClickListener(v -> SMSVerificationDialog.newInstance(userService.getLinkedMobile(true)).show(getSupportFragmentManager(), DIALOG_TAG_VERIFY_CODE));
  1024. findViewById(R.id.notice_sms_button_cancel).setOnClickListener(v -> GenericAlertDialog.newInstance(R.string.verify_title, R.string.really_cancel_verify, R.string.yes, R.string.no)
  1025. .show(getSupportFragmentManager(), DIALOG_TAG_CANCEL_VERIFY));
  1026. this.noticeSMSLayout.setVisibility(
  1027. userService.getMobileLinkingState() == UserService.LinkingState_PENDING ?
  1028. View.VISIBLE : View.GONE);
  1029. initOngoingCallNotice();
  1030. /*
  1031. * setup fragments
  1032. */
  1033. String initialFragmentTag = FRAGMENT_TAG_MESSAGES;
  1034. final int initialItemId;
  1035. final Fragment contactsFragment, messagesFragment, profileFragment;
  1036. Intent intent = getIntent();
  1037. if (intent != null && intent.getBooleanExtra(EXTRA_SHOW_CONTACTS, false)) {
  1038. initialFragmentTag = FRAGMENT_TAG_CONTACTS;
  1039. intent.removeExtra(EXTRA_SHOW_CONTACTS);
  1040. }
  1041. if (!isAppStart && savedInstanceState.containsKey(BUNDLE_CURRENT_FRAGMENT_TAG)) {
  1042. // restored session
  1043. initialFragmentTag = savedInstanceState.getString(BUNDLE_CURRENT_FRAGMENT_TAG, initialFragmentTag);
  1044. contactsFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_CONTACTS);
  1045. messagesFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_MESSAGES);
  1046. profileFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_PROFILE);
  1047. currentFragmentTag = initialFragmentTag;
  1048. switch (initialFragmentTag) {
  1049. case FRAGMENT_TAG_CONTACTS:
  1050. getSupportFragmentManager().beginTransaction().hide(messagesFragment).hide(profileFragment).show(contactsFragment).commit();
  1051. initialItemId = R.id.contacts;
  1052. break;
  1053. case FRAGMENT_TAG_MESSAGES:
  1054. getSupportFragmentManager().beginTransaction().hide(contactsFragment).hide(profileFragment).show(messagesFragment).commit();
  1055. initialItemId = R.id.messages;
  1056. break;
  1057. case FRAGMENT_TAG_PROFILE:
  1058. getSupportFragmentManager().beginTransaction().hide(messagesFragment).hide(contactsFragment).show(profileFragment).commit();
  1059. initialItemId = R.id.my_profile;
  1060. break;
  1061. default:
  1062. initialItemId = R.id.messages;
  1063. }
  1064. } else {
  1065. // new session
  1066. if (conversationService == null || !conversationService.hasConversations()) {
  1067. initialFragmentTag = FRAGMENT_TAG_CONTACTS;
  1068. }
  1069. contactsFragment = new ContactsSectionFragment();
  1070. messagesFragment = new MessageSectionFragment();
  1071. profileFragment = new MyIDFragment();
  1072. FragmentTransaction messagesTransaction = getSupportFragmentManager().beginTransaction().add(R.id.home_container, messagesFragment, FRAGMENT_TAG_MESSAGES);
  1073. FragmentTransaction contactsTransaction = getSupportFragmentManager().beginTransaction().add(R.id.home_container, contactsFragment, FRAGMENT_TAG_CONTACTS);
  1074. FragmentTransaction profileTransaction = getSupportFragmentManager().beginTransaction().add(R.id.home_container, profileFragment, FRAGMENT_TAG_PROFILE);
  1075. currentFragmentTag = initialFragmentTag;
  1076. switch (initialFragmentTag) {
  1077. case FRAGMENT_TAG_CONTACTS:
  1078. initialItemId = R.id.contacts;
  1079. messagesTransaction.hide(messagesFragment);
  1080. messagesTransaction.hide(profileFragment);
  1081. break;
  1082. case FRAGMENT_TAG_MESSAGES:
  1083. initialItemId = R.id.messages;
  1084. messagesTransaction.hide(contactsFragment);
  1085. messagesTransaction.hide(profileFragment);
  1086. break;
  1087. case FRAGMENT_TAG_PROFILE:
  1088. initialItemId = R.id.my_profile;
  1089. messagesTransaction.hide(messagesFragment);
  1090. messagesTransaction.hide(contactsFragment);
  1091. break;
  1092. default:
  1093. // should never happen
  1094. initialItemId = R.id.messages;
  1095. }
  1096. try {
  1097. messagesTransaction.commitAllowingStateLoss();
  1098. contactsTransaction.commitAllowingStateLoss();
  1099. profileTransaction.commitAllowingStateLoss();
  1100. } catch (IllegalStateException e) {
  1101. logger.error("Exception", e);
  1102. }
  1103. }
  1104. this.bottomNavigationView = findViewById(R.id.bottom_navigation);
  1105. this.bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
  1106. showMainContent();
  1107. // the contacts tab item badge uses custom colors (normally badges are red)
  1108. final @Nullable BadgeDrawable badgeDrawableContacts = bottomNavigationView.getBadge(R.id.contacts);
  1109. if (badgeDrawableContacts != null) {
  1110. badgeDrawableContacts.setBackgroundColor(getContactsTabBadgeColor(item.getItemId()));
  1111. }
  1112. Fragment currentFragment = getSupportFragmentManager().findFragmentByTag(currentFragmentTag);
  1113. if (currentFragment != null) {
  1114. if (item.getItemId() == R.id.contacts) {
  1115. logger.info("Contacts tab clicked");
  1116. if (!FRAGMENT_TAG_CONTACTS.equals(currentFragmentTag)) {
  1117. logger.info("Switching to Contacts tab");
  1118. getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.fast_fade_in, R.anim.fast_fade_out, R.anim.fast_fade_in, R.anim.fast_fade_out).hide(currentFragment).show(contactsFragment).commit();
  1119. currentFragmentTag = FRAGMENT_TAG_CONTACTS;
  1120. }
  1121. return true;
  1122. } else if (item.getItemId() == R.id.messages) {
  1123. logger.info("Messages tab clicked");
  1124. if (!FRAGMENT_TAG_MESSAGES.equals(currentFragmentTag)) {
  1125. logger.info("Switching to Messages tab");
  1126. getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.fast_fade_in, R.anim.fast_fade_out, R.anim.fast_fade_in, R.anim.fast_fade_out).hide(currentFragment).show(messagesFragment).commit();
  1127. currentFragmentTag = FRAGMENT_TAG_MESSAGES;
  1128. }
  1129. return true;
  1130. } else if (item.getItemId() == R.id.my_profile) {
  1131. logger.info("Profile tab clicked");
  1132. if (!FRAGMENT_TAG_PROFILE.equals(currentFragmentTag)) {
  1133. logger.info("Switching to My Profile tab");
  1134. getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.fast_fade_in, R.anim.fast_fade_out, R.anim.fast_fade_in, R.anim.fast_fade_out).hide(currentFragment).show(profileFragment).commit();
  1135. currentFragmentTag = FRAGMENT_TAG_PROFILE;
  1136. }
  1137. return true;
  1138. }
  1139. }
  1140. return false;
  1141. });
  1142. this.bottomNavigationView.post(() -> bottomNavigationView.setSelectedItemId(initialItemId));
  1143. final @Nullable Window window = getWindow();
  1144. if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  1145. window.setNavigationBarContrastEnforced(false);
  1146. }
  1147. updateBottomNavigation();
  1148. // restore sync adapter account if necessary
  1149. if (preferenceService.isSyncContacts()) {
  1150. if (!userService.checkAccount()) {
  1151. //create account
  1152. userService.getAccount(true);
  1153. }
  1154. userService.enableAccountAutoSync(true);
  1155. }
  1156. isInitialized = true;
  1157. showWhatsNew();
  1158. notificationService.cancelRestoreNotification();
  1159. if (preferenceService.getLastNotificationPermissionRequestTimestamp() == 0) {
  1160. ConfigUtils.requestNotificationPermission(this, notificationPermissionLauncher, preferenceService);
  1161. }
  1162. }
  1163. private void initOngoingCallNotice() {
  1164. this.ongoingCallNotice = findViewById(R.id.ongoing_call_notice);
  1165. updateOngoingCallNotice();
  1166. }
  1167. private void updateOngoingCallNotice() {
  1168. logger.debug("Update ongoing call notice");
  1169. GroupCallController groupCallController = groupCallManager != null
  1170. ? groupCallManager.getCurrentGroupCallController()
  1171. : null;
  1172. boolean hasRunningOOCall = VoipCallService.isRunning();
  1173. boolean hasRunningGroupCall = groupCallController != null;
  1174. if (hasRunningOOCall && hasRunningGroupCall) {
  1175. logger.warn("Invalid state: joined 1:1 AND group call, not showing call notice");
  1176. hideOngoingCallNotice();
  1177. } else if (hasRunningOOCall) {
  1178. showOngoingVoipCallNotice();
  1179. } else if (hasRunningGroupCall) {
  1180. showOngoingGroupCallNotice(groupCallController.getDescription());
  1181. } else {
  1182. logger.debug("No ongoing calls, hide notice");
  1183. hideOngoingCallNotice();
  1184. }
  1185. }
  1186. /**
  1187. * Hides the ongoing call notice not matter what type of called was displayed before
  1188. */
  1189. private void hideOngoingCallNotice() {
  1190. if (ongoingCallNotice != null) {
  1191. ongoingCallNotice.hide();
  1192. }
  1193. }
  1194. private void hideOngoingVoipCallNotice() {
  1195. if (ongoingCallNotice != null) {
  1196. ongoingCallNotice.hideVoip();
  1197. }
  1198. }
  1199. @AnyThread
  1200. private void showOngoingVoipCallNotice() {
  1201. if (ongoingCallNotice != null) {
  1202. ongoingCallNotice.showVoip();
  1203. }
  1204. }
  1205. @AnyThread
  1206. private void showOngoingGroupCallNotice(@NonNull GroupCallDescription call) {
  1207. if (!ConfigUtils.isGroupCallsEnabled()) {
  1208. return;
  1209. }
  1210. if (ongoingCallNotice != null && groupCallManager != null && groupCallManager.isJoinedCall(call)) {
  1211. ongoingCallNotice.showGroupCall(call, OngoingCallNoticeMode.MODE_GROUP_CALL_JOINED);
  1212. }
  1213. }
  1214. /**
  1215. * Ensure that Threema Push is enabled in the preferences.
  1216. */
  1217. private void enableThreemaPush() {
  1218. if (!preferenceService.useThreemaPush()) {
  1219. preferenceService.setUseThreemaPush(true);
  1220. ThreemaPushService.tryStart(logger, ThreemaApplication.getAppContext());
  1221. }
  1222. }
  1223. @SuppressLint("StaticFieldLeak")
  1224. private void updateDrawerImage() {
  1225. if (toolbar != null) {
  1226. new AsyncTask<Void, Void, Drawable>() {
  1227. @Override
  1228. protected Drawable doInBackground(Void... params) {
  1229. if (userService.getIdentity() == null) {
  1230. return null;
  1231. }
  1232. // TODO(ANDR-4021): don't create a fake contact model here
  1233. Bitmap bitmap = contactService.getAvatar(
  1234. // Create "fake" contact model for own user
  1235. ContactModel.create(userService.getIdentity(), userService.getPublicKey()),
  1236. new AvatarOptions.Builder()
  1237. .setReturnPolicy(AvatarOptions.DefaultAvatarPolicy.DEFAULT_FALLBACK)
  1238. .toOptions()
  1239. );
  1240. if (bitmap != null) {
  1241. int size = getResources().getDimensionPixelSize(R.dimen.navigation_icon_size);
  1242. return new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(bitmap, size, size, true));
  1243. }
  1244. return null;
  1245. }
  1246. @Override
  1247. protected void onPostExecute(Drawable drawable) {
  1248. if (drawable != null) {
  1249. toolbar.setNavigationIcon(drawable);
  1250. toolbar.setNavigationContentDescription(R.string.open_myid_popup);
  1251. }
  1252. }
  1253. }.execute();
  1254. }
  1255. }
  1256. @SuppressLint("StaticFieldLeak")
  1257. private void reallyCancelVerify() {
  1258. AnimationUtil.collapse(noticeSMSLayout, null, true);
  1259. new AsyncTask<Void, Void, Void>() {
  1260. @Override
  1261. protected Void doInBackground(Void... params) {
  1262. try {
  1263. userService.unlinkMobileNumber(TriggerSource.LOCAL);
  1264. } catch (Exception e) {
  1265. logger.error("Exception", e);
  1266. }
  1267. return null;
  1268. }
  1269. }.execute();
  1270. }
  1271. @Override
  1272. public boolean onCreateOptionsMenu(Menu menu) {
  1273. super.onCreateOptionsMenu(menu);
  1274. // Inflate the menu; this adds items to the action bar if it is present.
  1275. getMenuInflater().inflate(R.menu.activity_home, menu);
  1276. ConfigUtils.addIconsToOverflowMenu(menu);
  1277. return true;
  1278. }
  1279. @Override
  1280. public boolean onOptionsItemSelected(MenuItem item) {
  1281. Intent intent = null;
  1282. final int id = item.getItemId();
  1283. if (id == android.R.id.home) {
  1284. logger.info("Own avatar clicked");
  1285. showQRPopup();
  1286. return true;
  1287. } else if (id == R.id.menu_lock) {
  1288. logger.info("Lock button clicked");
  1289. lockAppService.lock();
  1290. return true;
  1291. } else if (id == R.id.menu_new_group) {
  1292. logger.info("New group button clicked");
  1293. intent = new Intent(this, GroupAddActivity.class);
  1294. } else if (id == R.id.menu_new_distribution_list) {
  1295. logger.info("New distribution list button clicked");
  1296. intent = new Intent(this, DistributionListAddActivity.class);
  1297. } else if (id == R.id.group_requests) {
  1298. logger.info("Group requests button clicked");
  1299. intent = new Intent(this, OutgoingGroupRequestActivity.class);
  1300. } else if (id == R.id.my_backups) {
  1301. logger.info("Backups button clicked");
  1302. intent = new Intent(this, BackupAdminActivity.class);
  1303. } else if (id == R.id.webclient) {
  1304. logger.info("Web button clicked");
  1305. intent = new Intent(this, SessionsActivity.class);
  1306. } else if (id == R.id.multi_device) {
  1307. logger.info("MD button clicked");
  1308. intent = new Intent(this, LinkedDevicesActivity.class);
  1309. } else if (id == R.id.scanner) {
  1310. logger.info("QR scanner button clicked");
  1311. intent = new Intent(this, BaseQrScannerActivity.class);
  1312. } else if (id == R.id.help) {
  1313. logger.info("Help button clicked");
  1314. intent = new Intent(this, SupportActivity.class);
  1315. } else if (id == R.id.settings) {
  1316. logger.info("Settings button clicked");
  1317. startActivityForResult(new Intent(this, SettingsActivity.class), ThreemaActivity.ACTIVITY_ID_SETTINGS);
  1318. } else if (id == R.id.directory) {
  1319. logger.info("Directory button clicked");
  1320. intent = new Intent(this, DirectoryActivity.class);
  1321. } else if (id == R.id.threema_channel) {
  1322. logger.info("Threema channel button clicked");
  1323. confirmThreemaChannel();
  1324. } else if (id == R.id.archived) {
  1325. logger.info("Archive button clicked");
  1326. intent = new Intent(this, ArchiveActivity.class);
  1327. } else if (id == R.id.globalsearch) {
  1328. logger.info("Global search button clicked");
  1329. intent = new Intent(this, GlobalSearchActivity.class);
  1330. } else if (id == R.id.starred_messages) {
  1331. logger.info("Starred messages button clicked");
  1332. intent = new Intent(this, StarredMessagesActivity.class);
  1333. }
  1334. if (intent != null) {
  1335. startActivity(intent);
  1336. }
  1337. return super.onOptionsItemSelected(item);
  1338. }
  1339. private void initActionBar() {
  1340. toolbar = findViewById(R.id.main_toolbar);
  1341. setSupportActionBar(toolbar);
  1342. actionBar = getSupportActionBar();
  1343. ViewExtensionsKt.applyDeviceInsetsAsPadding(
  1344. findViewById(R.id.appbar),
  1345. InsetSides.ltr()
  1346. );
  1347. AppCompatImageView toolbarLogoMain = toolbar.findViewById(R.id.toolbar_logo_main);
  1348. ViewGroup.LayoutParams layoutParams = toolbarLogoMain.getLayoutParams();
  1349. layoutParams.height = (int) (ConfigUtils.getActionBarSize(this) / 3.5);
  1350. toolbarLogoMain.setLayoutParams(layoutParams);
  1351. toolbarLogoMain.setImageResource(R.drawable.logo_main);
  1352. toolbarLogoMain.setColorFilter(ConfigUtils.getColorFromAttribute(this, R.attr.colorOnSurface), PorterDuff.Mode.SRC_IN);
  1353. toolbarLogoMain.setContentDescription(getString(R.string.logo));
  1354. toolbarLogoMain.setOnClickListener(v -> {
  1355. logger.info("Logo clicked");
  1356. if (currentFragmentTag != null) {
  1357. Fragment currentFragment = getSupportFragmentManager().findFragmentByTag(currentFragmentTag);
  1358. if (currentFragment != null && currentFragment.isAdded() && !currentFragment.isHidden()) {
  1359. if (currentFragment instanceof ContactsSectionFragment) {
  1360. ((ContactsSectionFragment) currentFragment).onLogoClicked();
  1361. } else if (currentFragment instanceof MessageSectionFragment) {
  1362. ((MessageSectionFragment) currentFragment).onLogoClicked();
  1363. } else if (currentFragment instanceof MyIDFragment) {
  1364. ((MyIDFragment) currentFragment).onLogoClicked();
  1365. }
  1366. }
  1367. }
  1368. });
  1369. updateDrawerImage();
  1370. }
  1371. @Override
  1372. public boolean onPrepareOptionsMenu(Menu menu) {
  1373. super.onPrepareOptionsMenu(menu);
  1374. if (serviceManager != null) {
  1375. MenuItem lockMenuItem = menu.findItem(R.id.menu_lock);
  1376. if (lockMenuItem != null) {
  1377. lockMenuItem.setVisible(
  1378. lockAppService.isLockingEnabled()
  1379. );
  1380. }
  1381. MenuItem privateChatToggleMenuItem = menu.findItem(R.id.menu_toggle_private_chats);
  1382. if (privateChatToggleMenuItem != null) {
  1383. if (preferenceService.isPrivateChatsHidden()) {
  1384. privateChatToggleMenuItem.setIcon(R.drawable.ic_outline_visibility);
  1385. privateChatToggleMenuItem.setTitle(R.string.title_show_private_chats);
  1386. } else {
  1387. privateChatToggleMenuItem.setIcon(R.drawable.ic_outline_visibility_off);
  1388. privateChatToggleMenuItem.setTitle(R.string.title_hide_private_chats);
  1389. }
  1390. ConfigUtils.tintMenuIcon(this, privateChatToggleMenuItem, R.attr.colorOnSurface);
  1391. }
  1392. Boolean addDisabled;
  1393. boolean webDisabled = false;
  1394. if (ConfigUtils.isWorkRestricted()) {
  1395. MenuItem backupsMenuItem = menu.findItem(R.id.my_backups);
  1396. if (backupsMenuItem != null) {
  1397. if (AppRestrictionUtil.isBackupsDisabled(this) || (AppRestrictionUtil.isDataBackupsDisabled(this) && ThreemaSafeMDMConfig.getInstance().isBackupDisabled())) {
  1398. backupsMenuItem.setVisible(false);
  1399. }
  1400. }
  1401. addDisabled = AppRestrictionUtil.getBooleanRestriction(getString(R.string.restriction__disable_add_contact));
  1402. webDisabled = AppRestrictionUtil.isWebDisabled(this);
  1403. } else {
  1404. addDisabled = this.contactService != null &&
  1405. this.contactService.getByIdentity(THREEMA_CHANNEL_IDENTITY) != null;
  1406. }
  1407. if (ConfigUtils.isWorkBuild()) {
  1408. MenuItem menuItem = menu.findItem(R.id.directory);
  1409. if (menuItem != null) {
  1410. menuItem.setVisible(ConfigUtils.isWorkDirectoryEnabled());
  1411. }
  1412. menuItem = menu.findItem(R.id.threema_channel);
  1413. if (menuItem != null) {
  1414. menuItem.setVisible(false);
  1415. }
  1416. } else if (addDisabled != null && addDisabled) {
  1417. MenuItem menuItem = menu.findItem(R.id.threema_channel);
  1418. if (menuItem != null) {
  1419. menuItem.setVisible(false);
  1420. }
  1421. }
  1422. if (ConfigUtils.supportsGroupLinks()) {
  1423. MenuItem menuItem = menu.findItem(R.id.scanner);
  1424. if (menuItem != null) {
  1425. menuItem.setVisible(true);
  1426. }
  1427. menuItem = menu.findItem(R.id.group_requests);
  1428. if (menuItem != null) {
  1429. menuItem.setVisible(true);
  1430. menu.setGroupVisible(menuItem.getGroupId(), true);
  1431. }
  1432. }
  1433. MenuItem webclientMenuItem = menu.findItem(R.id.webclient);
  1434. if (webclientMenuItem != null) {
  1435. webclientMenuItem.setVisible(!webDisabled);
  1436. }
  1437. // Id MD is currently locked, but was activated before, we still have to give access to the menu item
  1438. boolean mdMenuItemVisible = serviceManager.getMultiDeviceManager().isMultiDeviceActive() || ConfigUtils.isMultiDeviceEnabled(this);
  1439. MenuItem mdMenuItem = menu.findItem(R.id.multi_device);
  1440. if (mdMenuItem != null) {
  1441. mdMenuItem.setVisible(mdMenuItemVisible);
  1442. }
  1443. MenuItem starredMessagesItem = menu.findItem(R.id.starred_messages);
  1444. if (starredMessagesItem != null) {
  1445. String starredMessagesString = getString(R.string.starred_messages);
  1446. if (starredMessagesString != null) {
  1447. if (starredMessagesCount > 0) {
  1448. TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(getApplicationContext(), R.style.Threema_TextAppearance_StarredMessages_Count);
  1449. String starredMessagesCountString = starredMessagesCount > 99 ? "99+" : Long.toString(starredMessagesCount);
  1450. SpannableString spannableString = new SpannableString(String.format(Locale.US, starredMessagesString + " %s", starredMessagesCountString));
  1451. spannableString.setSpan(textAppearanceSpan, spannableString.length() - starredMessagesCountString.length(), spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  1452. starredMessagesItem.setTitle(spannableString);
  1453. } else {
  1454. starredMessagesItem.setTitle(starredMessagesString);
  1455. }
  1456. }
  1457. }
  1458. return true;
  1459. }
  1460. return false;
  1461. }
  1462. private void verifyPhoneCode(final String code) {
  1463. new AsyncTask<Void, Void, String>() {
  1464. @Override
  1465. protected String doInBackground(Void... params) {
  1466. try {
  1467. userService.verifyMobileNumber(code, TriggerSource.LOCAL);
  1468. } catch (LinkMobileNoException e) {
  1469. logger.error("Exception", e);
  1470. return getString(R.string.code_invalid);
  1471. } catch (Exception e) {
  1472. logger.error("Exception", e);
  1473. return getString(R.string.verify_failed_summary);
  1474. }
  1475. return null;
  1476. }
  1477. @Override
  1478. protected void onPostExecute(String result) {
  1479. if (result != null) {
  1480. getSupportFragmentManager().beginTransaction().add(SimpleStringAlertDialog.newInstance(R.string.error, result), "ss").commitAllowingStateLoss();
  1481. } else {
  1482. Toast.makeText(HomeActivity.this, getString(R.string.verify_success_text), Toast.LENGTH_LONG).show();
  1483. DialogUtil.dismissDialog(getSupportFragmentManager(), DIALOG_TAG_VERIFY_CODE, true);
  1484. }
  1485. }
  1486. }.execute();
  1487. }
  1488. @Override
  1489. public void onCallRequested(String tag) {
  1490. if (System.currentTimeMillis() < userService.getMobileLinkingTime() + PHONE_REQUEST_DELAY) {
  1491. SimpleStringAlertDialog.newInstance(R.string.verify_phonecall_text, getString(R.string.wait_one_minute)).show(getSupportFragmentManager(), "mi");
  1492. } else {
  1493. GenericAlertDialog.newInstance(R.string.verify_phonecall_text, R.string.prepare_call_message, R.string.ok, R.string.cancel).show(getSupportFragmentManager(), DIALOG_TAG_VERIFY_CODE_CONFIRM);
  1494. }
  1495. }
  1496. private void reallyRequestCall() {
  1497. new AsyncTask<Void, Void, String>() {
  1498. @Override
  1499. protected String doInBackground(Void... params) {
  1500. try {
  1501. userService.makeMobileLinkCall();
  1502. } catch (LinkMobileNoException e) {
  1503. logger.error("Exception", e);
  1504. return e.getMessage();
  1505. } catch (Exception e) {
  1506. logger.error("Exception", e);
  1507. return getString(R.string.verify_failed_summary);
  1508. }
  1509. return null;
  1510. }
  1511. @Override
  1512. protected void onPostExecute(String result) {
  1513. if (!TestUtil.isEmptyOrNull(result)) {
  1514. SimpleStringAlertDialog.newInstance(R.string.an_error_occurred, result).show(getSupportFragmentManager(), "le");
  1515. }
  1516. }
  1517. }.execute();
  1518. }
  1519. @Override
  1520. public void onYes(String tag, String code) {
  1521. switch (tag) {
  1522. case DIALOG_TAG_VERIFY_CODE:
  1523. verifyPhoneCode(code);
  1524. break;
  1525. default:
  1526. break;
  1527. }
  1528. }
  1529. @Override
  1530. public void onYes(String tag, Object data) {
  1531. switch (tag) {
  1532. case DIALOG_TAG_VERIFY_CODE_CONFIRM:
  1533. logger.info("Verify code confirmed");
  1534. reallyRequestCall();
  1535. break;
  1536. case DIALOG_TAG_CANCEL_VERIFY:
  1537. reallyCancelVerify();
  1538. break;
  1539. case DIALOG_TAG_SERIAL_LOCKED:
  1540. logger.info("Retrying entering valid license confirmed");
  1541. startActivityForResult(new Intent(this, EnterSerialActivity.class), ThreemaActivity.ACTIVITY_ID_ENTER_SERIAL);
  1542. finish();
  1543. break;
  1544. case DIALOG_TAG_FINISH_UP:
  1545. System.exit(0);
  1546. break;
  1547. case DIALOG_TAG_THREEMA_CHANNEL_VERIFY:
  1548. logger.info("Add Threema channel confirmed");
  1549. addThreemaChannel();
  1550. break;
  1551. case DIALOG_TAG_PASSWORD_PRESET_CONFIRM:
  1552. ThreemaSafeService threemaSafeService = getThreemaSafeService();
  1553. if (threemaSafeService != null) {
  1554. reconfigureSafe(threemaSafeService, (ThreemaSafeMDMConfig) data);
  1555. enableSafe(threemaSafeService, (ThreemaSafeMDMConfig) data, null);
  1556. }
  1557. break;
  1558. default:
  1559. break;
  1560. }
  1561. }
  1562. @Override
  1563. public void onNo(String tag) {
  1564. }
  1565. @Override
  1566. public void onNo(String tag, Object data) {
  1567. switch (tag) {
  1568. case DIALOG_TAG_SERIAL_LOCKED:
  1569. finish();
  1570. break;
  1571. case DIALOG_TAG_PASSWORD_PRESET_CONFIRM:
  1572. /* configuration change deferred */
  1573. break;
  1574. default:
  1575. break;
  1576. }
  1577. }
  1578. @Override
  1579. public void onResume() {
  1580. logger.info("onResume");
  1581. if (!isWhatsNewShown) {
  1582. ActivityService.activityResumed(this);
  1583. } else {
  1584. isWhatsNewShown = false;
  1585. }
  1586. MasterKey masterKey = ThreemaApplication.getMasterKey();
  1587. if (masterKey.isProtected()) {
  1588. if (!PassphraseService.isRunning()) {
  1589. PassphraseService.start(this);
  1590. }
  1591. }
  1592. if (serviceManager != null) {
  1593. RuntimeUtil.runOnWorkerThread(() -> {
  1594. FileService fileService = serviceManager.getFileService();
  1595. fileService.cleanTempDirs(2 * DateUtils.HOUR_IN_MILLIS);
  1596. });
  1597. try {
  1598. new UpdateStarredMessagesTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
  1599. } catch (RejectedExecutionException e) {
  1600. logger.error("Could not execute update starred message task", e);
  1601. }
  1602. }
  1603. super.onResume();
  1604. showMainContent();
  1605. updateWarningButton();
  1606. }
  1607. @Override
  1608. protected void onPause() {
  1609. logger.info("onPause");
  1610. super.onPause();
  1611. ActivityService.activityPaused(this);
  1612. }
  1613. @Override
  1614. public void onUserInteraction() {
  1615. ActivityService.activityUserInteract(this);
  1616. super.onUserInteraction();
  1617. }
  1618. @Override
  1619. protected void onActivityResult(
  1620. int requestCode, int resultCode,
  1621. Intent data) {
  1622. // http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
  1623. super.onActivityResult(requestCode, resultCode, data);
  1624. switch (requestCode) {
  1625. case ThreemaActivity.ACTIVITY_ID_WIZARDFIRST:
  1626. UserService userService = serviceManager.getUserService();
  1627. if (userService.hasIdentity()) {
  1628. showMainContent();
  1629. startHomeActivity(null);
  1630. } else {
  1631. finish();
  1632. }
  1633. break;
  1634. case ThreemaActivity.ACTIVITY_ID_ENTER_SERIAL:
  1635. if (serviceManager != null) {
  1636. LicenseService licenseService = serviceManager.getLicenseService();
  1637. if (!licenseService.isLicensed()) {
  1638. GenericAlertDialog.newInstance(R.string.enter_serial_title,
  1639. R.string.serial_required_want_exit,
  1640. R.string.try_again, R.string.cancel)
  1641. .show(getSupportFragmentManager(), DIALOG_TAG_SERIAL_LOCKED);
  1642. } else {
  1643. this.startHomeActivity(null);
  1644. }
  1645. }
  1646. break;
  1647. case ThreemaActivity.ACTIVITY_ID_SETTINGS:
  1648. this.invalidateOptionsMenu();
  1649. break;
  1650. case ThreemaActivity.ACTIVITY_ID_ID_SECTION:
  1651. if (resultCode == ThreemaActivity.RESULT_RESTART) {
  1652. Intent i = getIntent();
  1653. finish();
  1654. startActivity(i);
  1655. }
  1656. break;
  1657. case REQUEST_CODE_WHATSNEW:
  1658. showMainContent();
  1659. break;
  1660. case ThreemaActivity.ACTIVITY_ID_CONTACT_DETAIL:
  1661. case ThreemaActivity.ACTIVITY_ID_GROUP_DETAIL:
  1662. case ThreemaActivity.ACTIVITY_ID_COMPOSE_MESSAGE:
  1663. default:
  1664. break;
  1665. }
  1666. }
  1667. private void updateAppLogo() {
  1668. if (!ConfigUtils.isWorkBuild()) {
  1669. return;
  1670. }
  1671. File customAppIcon = serviceManager.getFileService().getAppLogo(
  1672. ConfigUtils.getAppThemeSettingFromDayNightMode(ConfigUtils.getCurrentDayNightMode(this))
  1673. );
  1674. if (customAppIcon.exists() && this.toolbar != null) {
  1675. ImageView headerImageView = toolbar.findViewById(R.id.toolbar_logo_main);
  1676. if (headerImageView != null) {
  1677. headerImageView.clearColorFilter();
  1678. Glide.with(this)
  1679. .load(customAppIcon)
  1680. .into(headerImageView);
  1681. }
  1682. }
  1683. }
  1684. @SuppressLint("StaticFieldLeak")
  1685. public void addThreemaChannel() {
  1686. final MessageService messageService;
  1687. try {
  1688. messageService = serviceManager.getMessageService();
  1689. } catch (Exception e) {
  1690. return;
  1691. }
  1692. backgroundExecutor.get().execute(
  1693. new BasicAddOrUpdateContactBackgroundTask(
  1694. THREEMA_CHANNEL_IDENTITY,
  1695. ContactModel.AcquaintanceLevel.DIRECT,
  1696. userService.getIdentity(),
  1697. apiConnector,
  1698. contactModelRepository,
  1699. AddContactRestrictionPolicy.IGNORE,
  1700. this,
  1701. ContactServiceImpl.THREEMA_PUBLIC_KEY
  1702. ) {
  1703. @Override
  1704. public void onBefore() {
  1705. GenericProgressDialog.newInstance(R.string.threema_channel, R.string.please_wait).show(getSupportFragmentManager(), THREEMA_CHANNEL_IDENTITY);
  1706. }
  1707. @Override
  1708. public void onFinished(@NonNull ContactResult result) {
  1709. DialogUtil.dismissDialog(getSupportFragmentManager(), THREEMA_CHANNEL_IDENTITY, true);
  1710. if (result instanceof ContactAvailable) {
  1711. // In case the contact has been successfully created or it has been
  1712. // modified, already verified, or already exists, the threema channel chat
  1713. // is launched.
  1714. launchThreemaChannelChat();
  1715. // Send initial messages to threema channel only if the threema channel has
  1716. // been newly created as a contact and did not exist before.
  1717. if (result instanceof ContactCreated) {
  1718. RuntimeUtil.runOnWorkerThread(() -> {
  1719. try {
  1720. ContactModel threemaChannelModel = contactService.getByIdentity(THREEMA_CHANNEL_IDENTITY);
  1721. if (threemaChannelModel == null) {
  1722. logger.error("Threema channel model is null after adding it");
  1723. return;
  1724. }
  1725. MessageReceiver<?> receiver = contactService.createReceiver(threemaChannelModel);
  1726. if (!getResources().getConfiguration().locale.getLanguage().startsWith("de") && !getResources().getConfiguration().locale.getLanguage().startsWith("gsw")) {
  1727. Thread.sleep(1000);
  1728. messageService.sendText("en", receiver);
  1729. Thread.sleep(500);
  1730. }
  1731. Thread.sleep(1000);
  1732. messageService.sendText(THREEMA_CHANNEL_START_NEWS_COMMAND, receiver);
  1733. Thread.sleep(1500);
  1734. messageService.sendText(ConfigUtils.isWorkBuild() ? THREEMA_CHANNEL_WORK_COMMAND : THREEMA_CHANNEL_START_ANDROID_COMMAND, receiver);
  1735. Thread.sleep(1500);
  1736. messageService.sendText(THREEMA_CHANNEL_INFO_COMMAND, receiver);
  1737. } catch (Exception e) {
  1738. //
  1739. }
  1740. });
  1741. }
  1742. } else {
  1743. Toast.makeText(HomeActivity.this, R.string.internet_connection_required, Toast.LENGTH_LONG).show();
  1744. }
  1745. }
  1746. }
  1747. );
  1748. }
  1749. private void launchThreemaChannelChat() {
  1750. Intent intent = new Intent(getApplicationContext(), ComposeMessageActivity.class);
  1751. IntentDataUtil.append(THREEMA_CHANNEL_IDENTITY, intent);
  1752. startActivity(intent);
  1753. }
  1754. @AnyThread
  1755. private void updateConnectionIndicator(final ConnectionState connectionState) {
  1756. logger.debug("connectionState = " + connectionState);
  1757. RuntimeUtil.runOnUiThread(() -> {
  1758. connectionIndicator = findViewById(R.id.connection_indicator);
  1759. if (connectionIndicator != null) {
  1760. ConnectionIndicatorUtil.getInstance().updateConnectionIndicator(connectionIndicator, connectionState);
  1761. }
  1762. });
  1763. }
  1764. private void confirmThreemaChannel() {
  1765. if (contactService.getByIdentity(THREEMA_CHANNEL_IDENTITY) == null) {
  1766. GenericAlertDialog.newInstance(R.string.threema_channel, R.string.threema_channel_intro, R.string.ok, R.string.cancel, 0).show(getSupportFragmentManager(), DIALOG_TAG_THREEMA_CHANNEL_VERIFY);
  1767. } else {
  1768. launchThreemaChannelChat();
  1769. }
  1770. }
  1771. /**
  1772. * @return The correct color to ensure contrast. If the navigation bar item is <strong>not</strong> selected, we use the {@code onSurfaceVariant}
  1773. * because the material library paints icons in {@code onSurfaceVariant}. So we match. If the item is selected, we have to use
  1774. * {@code onPrimaryContainer} since we customized our navigation bar active indicator to have the color of {@code primaryContainer}.
  1775. * @see "@style/Threema.BottomNavigationView"
  1776. */
  1777. @ColorInt
  1778. private int getContactsTabBadgeColor(@IdRes int currentlySelectedMenuItemId) {
  1779. return ConfigUtils.getColorFromAttribute(
  1780. HomeActivity.this,
  1781. currentlySelectedMenuItemId == R.id.contacts
  1782. ? R.attr.colorOnPrimaryContainer
  1783. : R.attr.colorOnSurfaceVariant
  1784. );
  1785. }
  1786. @Override
  1787. protected void onSaveInstanceState(@NonNull Bundle outState) {
  1788. try {
  1789. super.onSaveInstanceState(outState);
  1790. } catch (Exception e) {
  1791. if (logger != null) {
  1792. logger.error("Exception saving state", e);
  1793. }
  1794. }
  1795. if (currentFragmentTag != null) {
  1796. outState.putString(BUNDLE_CURRENT_FRAGMENT_TAG, currentFragmentTag);
  1797. }
  1798. }
  1799. }