Commit 5bbb02d8 authored by Romain Hunault's avatar Romain Hunault
Browse files

Merge branch 'aayush_fixup_microg' into 'master'

Resolve current MicroG EN issues

See merge request e/apps/apps!61
parents bbc047bc 4cd5e7e9
Pipeline #112317 passed with stage
in 5 minutes and 38 seconds
...@@ -20,6 +20,15 @@ android { ...@@ -20,6 +20,15 @@ android {
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
signingConfigs {
config {
storeFile file("../platform.keystore")
storePassword 'android'
keyAlias 'platform'
keyPassword 'android'
}
}
buildTypes { buildTypes {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
...@@ -28,6 +37,9 @@ android { ...@@ -28,6 +37,9 @@ android {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
platform {
signingConfig signingConfigs.config
}
} }
lintOptions { lintOptions {
lintConfig file("lint.xml") lintConfig file("lint.xml")
......
...@@ -75,14 +75,6 @@ ...@@ -75,14 +75,6 @@
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:theme="@style/AppTheme1" android:theme="@style/AppTheme1"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<provider
android:name=".MicroGProvider"
android:authorities="foundation.e.apps.micro.status"
android:exported="true"
/>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -49,8 +49,10 @@ import foundation.e.apps.search.SearchFragment ...@@ -49,8 +49,10 @@ import foundation.e.apps.search.SearchFragment
import foundation.e.apps.settings.SettingsFragment import foundation.e.apps.settings.SettingsFragment
import foundation.e.apps.updates.UpdatesFragment import foundation.e.apps.updates.UpdatesFragment
import foundation.e.apps.updates.UpdatesManager import foundation.e.apps.updates.UpdatesManager
import foundation.e.apps.utils.Common
import foundation.e.apps.utils.Constants import foundation.e.apps.utils.Constants
import foundation.e.apps.utils.Constants.CURRENTLY_SELECTED_FRAGMENT_KEY import foundation.e.apps.utils.Constants.CURRENTLY_SELECTED_FRAGMENT_KEY
import foundation.e.apps.utils.Constants.MICROG_SHARED_PREF
import foundation.e.apps.utils.PreferenceStorage import foundation.e.apps.utils.PreferenceStorage
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
...@@ -110,6 +112,8 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS ...@@ -110,6 +112,8 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
disableShiftingOfNabBarItems() disableShiftingOfNabBarItems()
Common.updateMicroGStatus(this)
initialiseUpdatesWorker() initialiseUpdatesWorker()
...@@ -132,15 +136,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS ...@@ -132,15 +136,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (retrieveStatus() != null) { Common.updateMicroGStatus(this)
if (retrieveStatus().equals("true")) {
PreferenceStorage(this).save(getString(R.string.prefs_microg_vrsn_installed), true)
} else {
PreferenceStorage(this).save(getString(R.string.prefs_microg_vrsn_installed), false)
}
} else {
PreferenceStorage(this).save(getString(R.string.prefs_microg_vrsn_installed), false)
}
} }
private fun openSearchFragment() { private fun openSearchFragment() {
...@@ -172,19 +168,6 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS ...@@ -172,19 +168,6 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
} }
private fun retrieveStatus(): String? {
var status: String? = null
val c: Cursor? = contentResolver.query(MicroGProvider.CONTENT_URI, null, "id=?", arrayOf("1"), "installStatus")
if (c!!.moveToFirst()) {
do {
status = c.getString(c.getColumnIndex("installStatus"))
} while (c.moveToNext())
}
c.close()
return status
}
private fun initialiseUpdatesWorker() { private fun initialiseUpdatesWorker() {
UpdatesManager(applicationContext).startWorker() UpdatesManager(applicationContext).startWorker()
......
/*
* Copyright (C) 2019-2021 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package foundation.e.apps;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import java.util.HashMap;
public class MicroGProvider extends ContentProvider {
public static final String PROVIDER_NAME = "foundation.e.apps.micro.status";
public static final String URL = "content://" + PROVIDER_NAME + "/cte";
public static final Uri CONTENT_URI = Uri.parse(URL);
public static final String id = "id";
public static final String installStatus = "installStatus";
public static final int uriCode = 1;
public static final UriMatcher uriMatcher;
private static HashMap<String, String> values;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "cte", uriCode);
uriMatcher.addURI(PROVIDER_NAME, "cte/*", uriCode);
}
@Override
public boolean onCreate() {
Context context = getContext();
DatabaseHelper dbHelper = new DatabaseHelper(context);
db = dbHelper.getWritableDatabase();
if (db != null) {
return true;
}
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(TABLE_NAME);
switch (uriMatcher.match(uri)) {
case uriCode:
qb.setProjectionMap(values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (sortOrder == null || sortOrder.equals("")) {
sortOrder = installStatus;
}
Cursor c = qb.query(db, projection, selection, selectionArgs, null,
null, sortOrder);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case uriCode:
return "vnd.android.cursor.dir/cte";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
long rowID = db.insert(TABLE_NAME, "", values);
if (rowID > 0) {
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(_uri, null);
return _uri;
}
throw new SQLException("Failed to add a record into " + uri);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
switch (uriMatcher.match(uri)) {
case uriCode:
count = db.delete(TABLE_NAME, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int count = 0;
switch (uriMatcher.match(uri)) {
case uriCode:
count = db.update(TABLE_NAME, values, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
private SQLiteDatabase db;
static final String DATABASE_NAME = "microGDB";
static final String TABLE_NAME = "microgtable";
static final int DATABASE_VERSION = 1;
static final String CREATE_DB_TABLE = " CREATE TABLE " + TABLE_NAME
+ " (id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ " installStatus TEXT NOT NULL);";
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_DB_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}
}
...@@ -27,10 +27,10 @@ import android.net.Uri ...@@ -27,10 +27,10 @@ import android.net.Uri
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import foundation.e.apps.MicroGProvider
import foundation.e.apps.R import foundation.e.apps.R
import foundation.e.apps.XAPK.FsUtils.deleteFileOrDir import foundation.e.apps.XAPK.FsUtils.deleteFileOrDir
import foundation.e.apps.utils.Constants import foundation.e.apps.utils.Constants
import foundation.e.apps.utils.Constants.MICROG_SHARED_PREF
import foundation.e.apps.utils.PreferenceStorage import foundation.e.apps.utils.PreferenceStorage
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
...@@ -126,7 +126,11 @@ class Installer(private val packageName: String, ...@@ -126,7 +126,11 @@ class Installer(private val packageName: String,
context.unregisterReceiver(receiver) context.unregisterReceiver(receiver)
Log.i(TAG, "Unregistered old broadcast receiver") Log.i(TAG, "Unregistered old broadcast receiver")
} catch (exception: Exception) { } catch (exception: Exception) {
exception.printStackTrace() if (exception !is IllegalArgumentException) {
exception.printStackTrace()
} else {
Log.d(TAG, "Broadcast receiver is already unregistered")
}
} }
context.registerReceiver(receiver, IntentFilter().apply { context.registerReceiver(receiver, IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED) addAction(Intent.ACTION_PACKAGE_ADDED)
...@@ -146,18 +150,7 @@ class Installer(private val packageName: String, ...@@ -146,18 +150,7 @@ class Installer(private val packageName: String,
callback.onInstallationComplete(context) callback.onInstallationComplete(context)
if (packageName == Constants.MICROG_PACKAGE) { if (packageName == Constants.MICROG_PACKAGE) {
PreferenceStorage(context).save(context.getString(R.string.prefs_microg_vrsn_installed), true) PreferenceStorage(context).save(MICROG_SHARED_PREF, true)
if (count(MicroGProvider.CONTENT_URI, context)) {
val values = ContentValues()
values.put(MicroGProvider.installStatus, "true")
val state=context.contentResolver.update(MicroGProvider.CONTENT_URI, values,
MicroGProvider.id + "=?", arrayOf("1"))
} else {
val values = ContentValues()
values.put(MicroGProvider.installStatus, "true")
val state=context.contentResolver.insert(MicroGProvider.CONTENT_URI, values);
}
} }
} }
} }
......
...@@ -23,7 +23,9 @@ import foundation.e.apps.R ...@@ -23,7 +23,9 @@ import foundation.e.apps.R
import foundation.e.apps.application.model.data.BasicData import foundation.e.apps.application.model.data.BasicData
import foundation.e.apps.application.model.data.SearchAppsBasicData import foundation.e.apps.application.model.data.SearchAppsBasicData
import foundation.e.apps.applicationmanager.ApplicationManager import foundation.e.apps.applicationmanager.ApplicationManager
import foundation.e.apps.utils.Common
import foundation.e.apps.utils.Constants import foundation.e.apps.utils.Constants
import foundation.e.apps.utils.Constants.MICROG_SHARED_PREF
import foundation.e.apps.utils.Error import foundation.e.apps.utils.Error
import foundation.e.apps.utils.PreferenceStorage import foundation.e.apps.utils.PreferenceStorage
import java.util.* import java.util.*
...@@ -37,12 +39,13 @@ class StateManager(private val info: ApplicationInfo, private val app: Applicati ...@@ -37,12 +39,13 @@ class StateManager(private val info: ApplicationInfo, private val app: Applicati
fun find(context: Context, basicData: BasicData) { fun find(context: Context, basicData: BasicData) {
if (basicData.name == Constants.MICROG) { if (basicData.name == Constants.MICROG) {
Log.e("MicroGStatus", PreferenceStorage(context).getBoolean(context.getString(R.string.prefs_microg_vrsn_installed), false).toString()) Common.updateMicroGStatus(context)
Log.e("MicroGStatus", PreferenceStorage(context).getBoolean(MICROG_SHARED_PREF, false).toString())
val state = if (appManager.isInstalling(app) && !app.isInstalling) { val state = if (appManager.isInstalling(app) && !app.isInstalling) {
State.DOWNLOADING State.DOWNLOADING
} else if (appManager.isInstalling(app) && app.isInstalling) { } else if (appManager.isInstalling(app) && app.isInstalling) {
State.INSTALLING State.INSTALLING
} else if (PreferenceStorage(context).getBoolean(context.getString(R.string.prefs_microg_vrsn_installed), false)) { } else if (PreferenceStorage(context).getBoolean(MICROG_SHARED_PREF, false)) {
if (info.isLastVersionInstalled(context, basicData.lastVersionNumber)) { if (info.isLastVersionInstalled(context, basicData.lastVersionNumber)) {
State.NOT_UPDATED State.NOT_UPDATED
} else { } else {
...@@ -75,7 +78,7 @@ class StateManager(private val info: ApplicationInfo, private val app: Applicati ...@@ -75,7 +78,7 @@ class StateManager(private val info: ApplicationInfo, private val app: Applicati
State.DOWNLOADING State.DOWNLOADING
} else if (appManager.isInstalling(app) && app.isInstalling) { } else if (appManager.isInstalling(app) && app.isInstalling) {
State.INSTALLING State.INSTALLING
} else if (PreferenceStorage(context).getBoolean(context.getString(R.string.prefs_microg_vrsn_installed), false)) { } else if (PreferenceStorage(context).getBoolean(MICROG_SHARED_PREF, false)) {
if (info.isLastVersionInstalled(context, basicData.lastVersionNumber)) { if (info.isLastVersionInstalled(context, basicData.lastVersionNumber)) {
State.NOT_UPDATED State.NOT_UPDATED
} else { } else {
......
...@@ -42,14 +42,6 @@ class SettingsFragment : PreferenceFragmentCompat() { ...@@ -42,14 +42,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
// Create preferences // Create preferences
setPreferencesFromResource(R.xml.preferences, rootKey) setPreferencesFromResource(R.xml.preferences, rootKey)
val microGInstallState = preferenceManager.findPreference<Preference>(getString(R.string.prefs_microg_vrsn_installed))
microGInstallState?.summary = if (context?.let { PreferenceStorage(it).getBoolean(getString(R.string.prefs_microg_vrsn_installed), false) }!!) {
getString(R.string.microg_installed)
} else {
getString(R.string.microg_not_installed)
}
// Handle update check interval changes // Handle update check interval changes
val updateCheckInterval = val updateCheckInterval =
preferenceManager.findPreference<Preference>(getString(R.string.pref_update_interval_key)) as ListPreference preferenceManager.findPreference<Preference>(getString(R.string.pref_update_interval_key)) as ListPreference
......
...@@ -26,6 +26,7 @@ import foundation.e.apps.application.model.Application ...@@ -26,6 +26,7 @@ import foundation.e.apps.application.model.Application
import foundation.e.apps.application.model.State import foundation.e.apps.application.model.State
import foundation.e.apps.applicationmanager.ApplicationManager import foundation.e.apps.applicationmanager.ApplicationManager
import foundation.e.apps.utils.Common import foundation.e.apps.utils.Common
import foundation.e.apps.utils.Constants.MICROG_SHARED_PREF
import foundation.e.apps.utils.PreferenceStorage import foundation.e.apps.utils.PreferenceStorage
class OutdatedApplicationsFileReader(private val packageManager: PackageManager, class OutdatedApplicationsFileReader(private val packageManager: PackageManager,
...@@ -37,7 +38,7 @@ class OutdatedApplicationsFileReader(private val packageManager: PackageManager, ...@@ -37,7 +38,7 @@ class OutdatedApplicationsFileReader(private val packageManager: PackageManager,
val application: Application? = loadMicroGVersion(context[0])[0] val application: Application? = loadMicroGVersion(context[0])[0]
println("versionname::-"+ application?.basicData!!.lastVersionNumber) println("versionname::-"+ application?.basicData!!.lastVersionNumber)
if (PreferenceStorage(context[0]) if (PreferenceStorage(context[0])
.getBoolean(context[0].getString(R.string.prefs_microg_vrsn_installed), false) .getBoolean(MICROG_SHARED_PREF, false)
&& application.state == State.NOT_UPDATED) { && application.state == State.NOT_UPDATED) {
applications.addAll(loadMicroGVersion(context[0])) applications.addAll(loadMicroGVersion(context[0]))
} }
......
...@@ -26,6 +26,8 @@ import foundation.e.apps.application.model.State ...@@ -26,6 +26,8 @@ import foundation.e.apps.application.model.State
import foundation.e.apps.applicationmanager.ApplicationManager import foundation.e.apps.applicationmanager.ApplicationManager
import foundation.e.apps.utils.Common import foundation.e.apps.utils.Common
import foundation.e.apps.utils.Constants import foundation.e.apps.utils.Constants
import foundation.e.apps.utils.Constants.MICROG_SHARED_PREF
import foundation.e.apps.utils.PreferenceStorage
class OutdatedApplicationsFinder(private val packageManager: PackageManager, class OutdatedApplicationsFinder(private val packageManager: PackageManager,
private val callback: UpdatesWorkerInterface, private val callback: UpdatesWorkerInterface,
...@@ -45,14 +47,20 @@ class OutdatedApplicationsFinder(private val packageManager: PackageManager, ...@@ -45,14 +47,20 @@ class OutdatedApplicationsFinder(private val packageManager: PackageManager,
private fun getOutdatedApplications(context: Context): ArrayList<Application> { private fun getOutdatedApplications(context: Context): ArrayList<Application> {
val result = ArrayList<Application>() val result = ArrayList<Application>()
var application: Application? = loadMicroGVersion(context)[0] val application: Application = loadMicroGVersion(context)[0]
if (application!!.state != State.INSTALLED) { if (PreferenceStorage(context).getBoolean(
MICROG_SHARED_PREF,
false
)
) {
result.add(application) result.add(application)
} }
val installedApplications = getInstalledApplications() val installedApplications = getInstalledApplications()
installedApplications.forEach { packageName -> installedApplications.forEach { packageName ->
val application = applicationManager.findOrCreateApp(packageName) if (packageName != Constants.MICROG_PACKAGE) {
verifyApplication(application, result, context) val app = applicationManager.findOrCreateApp(packageName)
verifyApplication(app, result, context)
}
} }
return result return result
} }
......
...@@ -32,6 +32,8 @@ import com.fasterxml.jackson.databind.ObjectMapper ...@@ -32,6 +32,8 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import foundation.e.apps.categories.model.Category import foundation.e.apps.categories.model.Category
import foundation.e.apps.utils.Constants.MICROG_PACKAGE
import foundation.e.apps.utils.Constants.MICROG_SHARED_PREF
import java.net.URL import java.net.URL
import java.nio.file.Paths import java.nio.file.Paths
import java.util.* import java.util.*
...@@ -127,6 +129,20 @@ object Common { ...@@ -127,6 +129,20 @@ object Common {
} }
return preferredLocaleList return preferredLocaleList
} }
/*
* Updates shared preferences related to microG EN
* @param context Context
*/
fun updateMicroGStatus(context: Context) {
val packageInfo = context.packageManager.getPackageInfo(MICROG_PACKAGE, 0)
val microgENversion = packageInfo.versionName
if (microgENversion.endsWith("-noen")) {
PreferenceStorage(context).save(MICROG_SHARED_PREF, false)
} else {
PreferenceStorage(context).save(MICROG_SHARED_PREF, true)
}
}
} }
class keyDeserializer : KeyDeserializer() { class keyDeserializer : KeyDeserializer() {
......
...@@ -36,6 +36,7 @@ object Constants { ...@@ -36,6 +36,7 @@ object Constants {
const val MICROG_ID = 149 const val MICROG_ID = 149
const val MICROG = "microG Exposure Notification version" const val MICROG = "microG Exposure Notification version"
const val MICROG_ICON_URI = "https://gitlab.e.foundation/uploads/-/system/project/avatar/149/ic_core_service_app.png?width=64" const val MICROG_ICON_URI = "https://gitlab.e.foundation/uploads/-/system/project/avatar/149/ic_core_service_app.png?width=64"
const val MICROG_SHARED_PREF = "pref_microg_installed"
// Search // Search
......
...@@ -98,10 +98,7 @@ ...@@ -98,10 +98,7 @@
<string name="action_installing">Wird installiert</string> <string name="action_installing">Wird installiert</string>
<string name="more">mehr</string> <string name="more">mehr</string>
<string name="exit">Bitte erneut auf ZURÜCK tippen zum Beenden</string> <string name="exit">Bitte erneut auf ZURÜCK tippen zum Beenden</string>
<string name="microg_installed">Installiert</string>
<string name="microg_not_installed">Nicht installiert</string>
<string name="app_package_name">Paketname</string> <string name="app_package_name">Paketname</string>
<string name="prefs_microg_vrsn_installed">pref_microg_installed</string>
<string name="Signature_verification_failed">Überprüfung der Signatur ist fehlgeschlagen, die Installation wird abgebrochen</string> <string name="Signature_verification_failed">Überprüfung der Signatur ist fehlgeschlagen, die Installation wird abgebrochen</string>
<string name="image_carousel">Bilder-Karussell</string> <string name="image_carousel">Bilder-Karussell</string>
<string name="error_incident">Ein Fehler ist aufgetreten</string> <string name="error_incident">Ein Fehler ist aufgetreten</string>
......
...@@ -98,8 +98,5 @@ ...@@ -98,8 +98,5 @@
<string name="app_trackers_title">Aztarnariak</string> <string name="app_trackers_title">Aztarnariak</string>
<string name="app_privacy_description">Puntuazioa 10etik. <a href="https://exodus-privacy.eu.org">Exodus Privacy analisiak</a> erabiliz kalkulatuta, aplikazioan erabilitako baimen eta aztarnarietan oinarrituta.</string> <string name="app_privacy_description">Puntuazioa 10etik. <a href="https://exodus-privacy.eu.org">Exodus Privacy analisiak</a> erabiliz kalkulatuta, aplikazioan erabilitako baimen eta aztarnarietan oinarrituta.</string>
<string name="app_rating_description">Puntuazioa 5etik. Erabiltzaileek aplikazioari emandako puntuazioarekin kalkulatuta.</string> <string name="app_rating_description">Puntuazioa 5etik. Erabiltzaileek aplikazioari emandako puntuazioarekin kalkulatuta.</string>
<string name="microg_installed">Instalatuta</string>
<string name="microg_not_installed">Instalatu gabe</string>