FreeRDP
Loading...
Searching...
No Matches
BookmarkViewModel.java
1/*
2 * ViewModel for BookmarkActivity — handles asynchronous data loading and saving, keeping database
3 * and file I/O off the main thread.
4 *
5 * Copyright 2026 Ibrahim Sevinc <ibrahim.sevinc.mail@gmail.com>
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
8 * If a copy of the MPL was not distributed with this file, You can obtain one at
9 * http://mozilla.org/MPL/2.0/.
10 */
11
12package com.freerdp.freerdpcore.presentation;
13
14import android.app.Application;
15import android.content.SharedPreferences;
16import android.net.Uri;
17import android.os.Bundle;
18import android.util.Log;
19
20import androidx.annotation.NonNull;
21import androidx.lifecycle.AndroidViewModel;
22import androidx.lifecycle.LiveData;
23import androidx.lifecycle.MutableLiveData;
24
25import com.freerdp.freerdpcore.data.AppDatabase;
26import com.freerdp.freerdpcore.data.HistoryDatabase;
27import com.freerdp.freerdpcore.domain.BookmarkBase;
28import com.freerdp.freerdpcore.domain.ConnectionReference;
29import com.freerdp.freerdpcore.services.ManualBookmarkGateway;
30import com.freerdp.freerdpcore.services.QuickConnectHistoryGateway;
31import com.freerdp.freerdpcore.utils.RDPFileHelper;
32
33import java.io.File;
34import java.io.IOException;
35import java.util.concurrent.ExecutorService;
36import java.util.concurrent.Executors;
37
38public class BookmarkViewModel extends AndroidViewModel
39{
40 private static final String TAG = "BookmarkViewModel";
41
42 // LiveData for observing state changes from the Activity
43 private final MutableLiveData<BookmarkBase> bookmarkLiveData = new MutableLiveData<>();
44 private final MutableLiveData<Boolean> saveCompleteEvent = new MutableLiveData<>();
45
46 private boolean settingsChanged = false;
47 private boolean newBookmark = false;
48
49 // Single-thread executor handles Room DB and File parsing off the UI thread
50 private final ExecutorService executor = Executors.newSingleThreadExecutor();
51
52 private final ManualBookmarkGateway manualBookmarkGateway;
53 private final QuickConnectHistoryGateway quickConnectHistoryGateway;
54
55 public BookmarkViewModel(@NonNull Application application)
56 {
57 super(application);
58 manualBookmarkGateway =
59 new ManualBookmarkGateway(AppDatabase.getInstance(application).bookmarkDao());
60 quickConnectHistoryGateway =
61 new QuickConnectHistoryGateway(HistoryDatabase.getInstance(application).historyDao());
62 }
63
64 public LiveData<BookmarkBase> getBookmarkLiveData()
65 {
66 return bookmarkLiveData;
67 }
68
69 public LiveData<Boolean> getSaveCompleteEvent()
70 {
71 return saveCompleteEvent;
72 }
73
74 public BookmarkBase getBookmark()
75 {
76 return bookmarkLiveData.getValue();
77 }
78
79 public boolean isSettingsChanged()
80 {
81 return settingsChanged;
82 }
83
84 public void setSettingsChanged(boolean changed)
85 {
86 settingsChanged = changed;
87 }
88
89 public boolean isNewBookmark()
90 {
91 return newBookmark;
92 }
93
94 public void setNewBookmark(boolean isNew)
95 {
96 newBookmark = isNew;
97 }
98
99 // -------------------------------------------------------------------------
100 // Async Operations
101 // -------------------------------------------------------------------------
102
103 public void loadBookmark(Bundle bundle, SharedPreferences prefs)
104 {
105 if (bookmarkLiveData.getValue() != null)
106 return;
107
108 executor.execute(() -> {
109 BookmarkBase bookmark = null;
110 boolean isNew = false;
111
112 if (bundle != null && bundle.containsKey(BookmarkActivity.PARAM_CONNECTION_REFERENCE))
113 {
114 String refStr = bundle.getString(BookmarkActivity.PARAM_CONNECTION_REFERENCE);
115
116 if (ConnectionReference.isBookmarkReference(refStr))
117 {
118 bookmark =
119 manualBookmarkGateway.findById(ConnectionReference.getBookmarkId(refStr));
120 isNew = false;
121 }
122 else if (ConnectionReference.isHostnameReference(refStr))
123 {
124 bookmark = new BookmarkBase();
125 bookmark.setLabel(ConnectionReference.getHostname(refStr));
126 bookmark.setHostname(ConnectionReference.getHostname(refStr));
127 isNew = true;
128 }
129 else if (ConnectionReference.isFileReference(refStr))
130 {
131 String fileRef = ConnectionReference.getFile(refStr);
132 bookmark = new BookmarkBase();
133 String name = Uri.decode(new File(fileRef).getName());
134 String lower = name.toLowerCase(java.util.Locale.ROOT);
135 if (lower.endsWith(".rdp"))
136 name = name.substring(0, name.length() - 4);
137 else if (lower.endsWith(".rdpw"))
138 name = name.substring(0, name.length() - 5);
139 bookmark.setLabel(name);
140 try
141 {
142 Uri uri = Uri.parse(fileRef);
143 RDPFileHelper.importFrom(
144 getApplication().getContentResolver().openInputStream(uri), bookmark);
145 }
146 catch (IOException e)
147 {
148 Log.e(TAG, "Failed reading RDP file", e);
149 }
150 isNew = true;
151 }
152 }
153
154 if (bookmark == null)
155 {
156 bookmark = new BookmarkBase();
157 }
158
159 this.newBookmark = isNew;
160 this.settingsChanged = false;
161
162 // Clear TEMP SharedPreferences and write fresh data BEFORE posting to UI
163 prefs.edit().clear().apply();
164 bookmark.writeToSharedPreferences(prefs);
165
166 // Notify Activity that loading is complete on the Main Thread
167 bookmarkLiveData.postValue(bookmark);
168 });
169 }
170
171 public void saveBookmark(SharedPreferences prefs)
172 {
173 BookmarkBase bookmark = getBookmark();
174 if (bookmark == null)
175 return;
176
177 executor.execute(() -> {
178 bookmark.readFromSharedPreferences(prefs);
179
180 if (bookmark.getType() == BookmarkBase.TYPE_MANUAL)
181 {
182 quickConnectHistoryGateway.removeHistoryItem(bookmark.getHostname());
183
184 if (bookmark.getId() > 0)
185 {
186 manualBookmarkGateway.update(bookmark);
187 }
188 else
189 {
190 manualBookmarkGateway.insert(bookmark);
191 }
192 }
193
194 // Notify Activity to close
195 saveCompleteEvent.postValue(true);
196 });
197 }
198
199 @Override protected void onCleared()
200 {
201 super.onCleared();
202 executor.shutdown(); // Clean up threads when ViewModel dies
203 }
204}