Initial v4

This commit is contained in:
Rahul Kumar Patel
2021-02-15 04:52:56 +05:30
commit dfc706b13e
472 changed files with 35142 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gitlab files
.gitlab/
# Gradle files
.gradle/
build/
/*/build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# From .gitignore generated by Android Studio
*.iml
.DS_Store
/captures
/.idea
/.gradle*
gradle-app.setting
*.zip
app/release/*
app/beta/*
app/alpha/*

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

166
app/build.gradle Normal file
View File

@@ -0,0 +1,166 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.aurora.store"
minSdkVersion 19
targetSdkVersion 30
versionCode 30
versionName "4.0.1"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
beta {
initWith release
applicationIdSuffix = ".beta"
}
debug {
applicationIdSuffix = ".debug"
}
}
buildFeatures {
viewBinding true
}
kotlinOptions {
jvmTarget = '1.8'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
buildscript {
ext {
versions = [
okhttp3 : "4.9.0",
fetch2 : "3.1.6",
fuel : "2.3.0",
glide : "4.11.0",
lifecycle: "2.3.0",
epoxy : "4.3.1",
libsu : "2.5.1"
]
}
}
kapt {
correctErrorTypes = true
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//MultiDex for Kitkat support
implementation("androidx.multidex:multidex:2.0.1")
//Protobuf
implementation("com.google.protobuf:protobuf-java:3.14.0")
//Apache's Goodies
implementation("commons-io:commons-io:2.7")
implementation("org.apache.commons:commons-text:1.8")
//Google's Goodies
implementation("com.google.android.material:material:1.3.0")
implementation("com.google.android:flexbox:2.0.1")
implementation("com.google.code.gson:gson:2.8.6")
//AndroidX
implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.viewpager2:viewpager2:1.0.0")
implementation("androidx.vectordrawable:vectordrawable:1.1.0")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
//Arch LifeCycle
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${versions.lifecycle}")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.lifecycle}")
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${versions.lifecycle}")
//Arch Navigation
implementation("androidx.navigation:navigation-fragment-ktx:${navigation}")
implementation("androidx.navigation:navigation-runtime-ktx:${navigation}")
implementation("androidx.navigation:navigation-ui-ktx:${navigation}")
//UI Addons
implementation("com.github.florent37:expansionpanel:1.2.4")
//Easy Permission
implementation("com.github.quickpermissions:quickpermissions-kotlin:0.4.0")
//Glide
implementation("com.github.bumptech.glide:glide:${versions.glide}")
kapt("com.github.bumptech.glide:compiler:${versions.glide}")
//Shimmer
implementation("com.facebook.shimmer:shimmer:0.5.0")
//Epoxy
implementation("com.airbnb.android:epoxy:${versions.epoxy}")
kapt("com.airbnb.android:epoxy-processor:${versions.epoxy}")
//Merlin
implementation("com.novoda:merlin:1.2.0")
//HTTP Clients
implementation("com.github.kittinunf.fuel:fuel:${versions.fuel}")
implementation("com.squareup.okhttp3:okhttp:${versions.okhttp3}")
//Fetch - Downloader
implementation "androidx.tonyodev.fetch2:xfetch2:${versions.fetch2}"
//Kovenant
implementation("nl.komponents.kovenant:kovenant:3.3.0")
implementation("nl.komponents.kovenant:kovenant-android:3.3.0")
//EventBus
implementation("org.greenrobot:eventbus:3.2.0")
//Lib-SU
implementation "com.github.topjohnwu.libsu:core:${versions.libsu}"
//Love <3
api("com.gitlab.AuroraOSS:gplayapi:514f061739")
}

120
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,120 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
# Same story for the standard library's SafeContinuation that also uses AtomicReferenceFieldUpdater
-keepclassmembernames class kotlin.coroutines.SafeContinuation {
volatile <fields>;
}
# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when
# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used.
-dontwarn java.lang.instrument.ClassFileTransformer
-dontwarn sun.misc.SignalHandler
-dontwarn java.lang.instrument.Instrumentation
-dontwarn sun.misc.Signal
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform
-dontwarn org.conscrypt.ConscryptHostnameVerifier
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
#Kovenant
-dontwarn rx.internal.util.unsafe.**
-dontwarn nl.komponents.kovenant.unsafe.**
-dontwarn okio.**
-keep class com.google.**
-dontwarn com.google.**
-keep class com.google.gson.Gson {*;}
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
#Event Bus
-keepattributes *Annotation*
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
-keepclassmembers enum * { *; }
-keep class com.aurora.store.view.ui.preferences.**
-dontwarn com.aurora.store.view.ui.preferences.**
-keepclassmembers class * {
private <fields>;
}
#GPlay API
-keep public class com.aurora.gplayapi.** {
*;
}

View File

@@ -0,0 +1,41 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.aurora.aurorastore", appContext.packageName)
}
}

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Aurora Store
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
~
~ Aurora Store 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 2 of the License, or
~ (at your option) any later version.
~
~ Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
~
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.aurora.store">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission
android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.DELETE_PACKAGES"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" /> <!-- MIUI -->
<uses-permission android:name="com.miui.securitycenter.permission.GLOBAL_PACKAGEINSTALLER" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<application
android:name=".AuroraApplication"
android:allowBackup="true"
android:banner="@drawable/ic_launcher_background"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:targetApi="m">
<activity
android:name=".view.ui.onboarding.OnboardingActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".view.ui.splash.SplashActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name=".MainActivity"
android:launchMode="singleTask" />
<activity android:name=".view.ui.search.SearchSuggestionActivity" />
<activity android:name=".view.ui.search.SearchResultsActivity" />
<activity android:name=".view.ui.commons.StreamBrowseActivity" />
<activity android:name=".view.ui.sale.AppSalesActivity" />
<activity android:name=".view.ui.details.AppDetailsActivity" />
<activity android:name=".view.ui.details.ScreenshotActivity" />
<activity android:name=".view.ui.all.AppsGamesActivity" />
<activity android:name=".view.ui.commons.CategoryBrowseActivity" />
<activity android:name=".view.ui.details.DetailsMoreActivity" />
<activity android:name=".view.ui.details.DetailsReviewActivity" />
<activity android:name=".view.ui.downloads.DownloadActivity" />
<activity android:name=".view.ui.account.GoogleActivity" />
<activity android:name=".view.ui.spoof.SpoofActivity" />
<activity android:name=".view.ui.account.AccountActivity" />
<activity android:name=".view.ui.details.DetailsExodusActivity" />
<activity android:name=".view.ui.details.DevAppsActivity" />
<activity android:name=".view.ui.commons.BlacklistActivity" />
<activity android:name=".view.ui.preferences.SettingsActivity" />
<activity android:name=".view.ui.about.AboutActivity" />
<service android:name=".data.service.NotificationService" />
<service android:name=".data.installer.InstallerService" />
<service
android:name="com.novoda.merlin.MerlinService"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/paths" />
</provider>
<receiver android:name=".data.receiver.PackageManagerReceiver" />
<receiver android:name=".data.receiver.DownloadResumeReceiver" />
<receiver android:name=".data.receiver.DownloadPauseReceiver" />
<receiver android:name=".data.receiver.DownloadCancelReceiver" />
</application>
</manifest>

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2015-2016 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.aurora.services;
interface IPrivilegedCallback {
void handleResult(
in String packageName,
in int returnCode
);
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2015-2016 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.aurora.services;
import com.aurora.services.IPrivilegedCallback;
interface IPrivilegedService {
boolean hasPrivilegedPermissions();
oneway void installPackage(
in String packageName,
in Uri uri,
in int flags,
in String installerPackageName,
in IPrivilegedCallback callback
);
oneway void installSplitPackage(
in String packageName,
in List<Uri> uriList,
in int flags,
in String installerPackageName,
in IPrivilegedCallback callback
);
oneway void deletePackage(
in String packageName,
in int flags,
in IPrivilegedCallback callback
);
}

50
app/src/main/assets/accent.json Executable file
View File

@@ -0,0 +1,50 @@
[
{
"id": "1",
"accent": "#6C63FF"
},
{
"id": "2",
"accent": "#FF00FF"
},
{
"id": "3",
"accent": "#F50057"
},
{
"id": "4",
"accent": "#F9A826"
},
{
"id": "5",
"accent": "#49A942"
},
{
"id": "6",
"accent": "#6633cc"
},
{
"id": "7",
"accent": "#52565e"
},
{
"id": "8",
"accent": "#ee70a6"
},
{
"id": "9",
"accent": "#b5c327"
},
{
"id": "10",
"accent": "#f38654"
},
{
"id": "11",
"accent": "#61A7E0"
},
{
"id": "12",
"accent": "#7289da"
}
]

37
app/src/main/assets/dash.json Executable file
View File

@@ -0,0 +1,37 @@
[
{
"id": "0",
"title": "FAQs",
"subtitle": "Have questions? Find out the answers.",
"icon": "ic_faq",
"url": "https://gitlab.com/AuroraOSS/AuroraStore"
},
{
"id": "1",
"title": "Source code",
"icon": "ic_code",
"subtitle": "Find out what's inside.",
"url": "https://gitlab.com/AuroraOSS/AuroraStore/-/blob/master"
},
{
"id": "2",
"title": "License",
"subtitle": "Yes we do have one ;)",
"icon": "ic_license",
"url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/LICENSE"
},
{
"id": "3",
"title": "Privacy",
"subtitle": "Find out if we have your nudes :p",
"icon": "ic_privacy",
"url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/PRIVACY"
},
{
"id": "4",
"title": "Disclaimer",
"subtitle": "Hold your own beer!",
"icon": "ic_disclaimer",
"url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/DISCLAIMER"
}
]

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
[
{
"id": "0",
"title": "Session installer",
"subtitle": "Session based installer for bundled/split APKs",
"description": "Best suited for devices running Android 5.0+.",
"url": "https://developer.android.com/reference/android/content/pm/PackageInstaller.Session"
},
{
"id": "1",
"title": "Native installer",
"subtitle": "Intent based installer, available on all devices",
"description": "Best suited for devices running below Android 4.4 or OEM modified ROMs like MIUI, One-UI.",
"url": "https://developer.android.com/reference/android/content/Intent#setDataAndType"
},
{
"id": "2",
"title": "Root installer",
"subtitle": "Installer for background installations",
"description": "Requires root, supports all Android versions."
},
{
"id": "3",
"title": "Aurora Service",
"subtitle": "Installer for background installations",
"description": "Requires Aurora Services to be installed as system app."
}
]

32
app/src/main/assets/themes.json Executable file
View File

@@ -0,0 +1,32 @@
[
{
"id": "0",
"title": "System",
"subtitle": "Follow system themes."
},
{
"id": "1",
"title": "Light",
"subtitle": "White UI must die, may slap on your face at night."
},
{
"id": "2",
"title": "Dark",
"subtitle": "As dark as your humour, suitable for night-owls."
},
{
"id": "3",
"title": "Pitch Black",
"subtitle": "The black, that matters."
},
{
"id": "4",
"title": "Dark-X",
"subtitle": "The dark, that looks cool?"
},
{
"id": "5",
"title": "Disskord",
"subtitle": "Kanged from discord"
}
]

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,51 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora
object Constants {
const val INT_EXTRA = "INT_EXTRA"
const val FLOAT_EXTRA = "FLOAT_EXTRA"
const val STRING_APP = "STRING_APP"
const val STRING_EXTRA = "STRING_EXTRA"
const val BROWSE_EXTRA = "BROWSE_EXTRA"
const val FETCH_GROUP_ID = "FETCH_GROUP_ID"
const val CONNECTIVITY_CHECK_URL = "https://connectivitycheck.android.com/generate_204"
const val EXODUS_BASE_URL = "https://reports.exodus-privacy.eu.org/api/search/"
const val EXODUS_REPORT_URL = "https://reports.exodus-privacy.eu.org/reports/"
const val SHARE_URL = "http://play.google.com/store/apps/details?id="
const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT"
const val NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL"
const val URL_DISPENSER = "http://goolag.store:1337/api/auth"
//ACCOUNTS
const val ACCOUNT_SIGNED_IN = "ACCOUNT_SIGNED_IN"
const val ACCOUNT_SIGNED_TIMESTAMP = "ACCOUNT_SIGNED_TIMESTAMP"
const val ACCOUNT_TYPE = "ACCOUNT_TYPE"
const val ACCOUNT_EMAIL_PLAIN = "ACCOUNT_EMAIL_PLAIN"
const val ACCOUNT_AAS_PLAIN = "ACCOUNT_AAS_PLAIN"
const val PAGE_TYPE = "PAGE_TYPE"
const val TOP_CHART_TYPE = "TOP_CHART_TYPE"
const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY"
}

View File

@@ -0,0 +1,78 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDexApplication
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.providers.NetworkProvider
import com.aurora.store.data.receiver.PackageManagerReceiver
import com.aurora.store.data.service.NotificationService
import com.aurora.store.util.CommonUtil
import com.aurora.store.util.PackageUtil
import com.tonyodev.fetch2.Fetch
import nl.komponents.kovenant.android.startKovenant
import nl.komponents.kovenant.android.stopKovenant
class AuroraApplication : MultiDexApplication() {
private lateinit var fetch: Fetch
private lateinit var packageManagerReceiver: PackageManagerReceiver
override fun onCreate() {
super.onCreate()
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
NotificationService.startService(this)
fetch = DownloadManager.with(this).fetch
packageManagerReceiver = object : PackageManagerReceiver() {
}
//Register broadcast receiver for package install/uninstall
registerReceiver(packageManagerReceiver, PackageUtil.getFilter())
NetworkProvider
.with(this)
.bind()
startKovenant()
CommonUtil.cleanupInstallationSessions(applicationContext)
}
override fun onTerminate() {
NetworkProvider
.with(this)
.unbind()
stopKovenant()
super.onTerminate()
}
override fun onLowMemory() {
NetworkProvider
.with(this)
.unbind()
super.onLowMemory()
}
}

View File

@@ -0,0 +1,53 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store
import android.content.Context
import android.graphics.Bitmap
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.cache.LruResourceCache
import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.signature.ObjectKey
@GlideModule
class AuroraGlide : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
val memoryCacheSizeBytes = 1024 * 1024 * 50
builder.setMemoryCache(LruResourceCache(memoryCacheSizeBytes.toLong()))
builder.setDiskCache(InternalCacheDiskCacheFactory(context, memoryCacheSizeBytes.toLong()))
builder.setDefaultRequestOptions(requestOptions(context))
}
companion object {
private fun requestOptions(context: Context): RequestOptions {
return RequestOptions()
.signature(ObjectKey(System.currentTimeMillis() / (24 * 60 * 60 * 1000)))
.centerCrop()
.encodeFormat(Bitmap.CompressFormat.PNG)
.encodeQuality(60)
.format(DecodeFormat.PREFER_ARGB_8888)
.skipMemoryCache(false)
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store
enum class AccountType {
ANONYMOUS,
GOOGLE
}

View File

@@ -0,0 +1,243 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store
import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.IdRes
import androidx.annotation.NonNull
import androidx.core.graphics.ColorUtils
import androidx.core.view.GravityCompat
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.Navigation
import androidx.navigation.ui.NavigationUI
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.databinding.ActivityMainBinding
import com.aurora.store.util.Log
import com.aurora.store.util.ViewUtil
import com.aurora.store.util.ViewUtil.getStyledAttribute
import com.aurora.store.util.extensions.isQAndAbove
import com.aurora.store.util.extensions.load
import com.aurora.store.util.extensions.open
import com.aurora.store.view.ui.about.AboutActivity
import com.aurora.store.view.ui.account.AccountActivity
import com.aurora.store.view.ui.all.AppsGamesActivity
import com.aurora.store.view.ui.commons.BaseActivity
import com.aurora.store.view.ui.commons.BlacklistActivity
import com.aurora.store.view.ui.downloads.DownloadActivity
import com.aurora.store.view.ui.preferences.SettingsActivity
import com.aurora.store.view.ui.sale.AppSalesActivity
import com.aurora.store.view.ui.search.SearchSuggestionActivity
import com.aurora.store.view.ui.spoof.SpoofActivity
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions
class MainActivity : BaseActivity() {
private lateinit var B: ActivityMainBinding
private lateinit var navController: NavController
private lateinit var authData: AuthData
private var lastBackPressed = 0L
companion object {
@JvmStatic
private fun matchDestination(
@NonNull destination: NavDestination?,
@IdRes destId: Int
): Boolean {
var currentDestination = destination
while (currentDestination?.id != destId && currentDestination?.parent != null) {
currentDestination = currentDestination.parent
}
return currentDestination?.id == destId
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
B = ActivityMainBinding.inflate(layoutInflater)
setContentView(B.root)
authData = AuthProvider.with(this).getAuthData()
attachToolbar()
attachNavigation()
attachDrawer()
attachSearch()
checkPermission()
}
private fun checkPermission() = runWithPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) {
Log.i("External Storage Access Available")
if (isQAndAbove()) {
//pickExternalFileDir()
}
}
private fun attachToolbar() {
B.viewToolbar.imgActionPrimary.setOnClickListener {
B.drawerLayout.openDrawer(GravityCompat.START, true)
}
B.viewToolbar.imgActionSecondary.setOnClickListener {
val userAppIntent = Intent(this, DownloadActivity::class.java)
startActivity(userAppIntent, ViewUtil.getEmptyActivityBundle(this))
}
}
private fun attachSearch() {
B.searchFab.setOnClickListener {
startActivity(
Intent(this, SearchSuggestionActivity::class.java),
ViewUtil.getEmptyActivityBundle(this)
)
}
}
private fun attachNavigation() {
val bottomNavigationView: BottomNavigationView = B.navView
navController = Navigation.findNavController(this, R.id.nav_host_fragment)
val backGroundColor = getStyledAttribute(this, android.R.attr.colorBackground)
bottomNavigationView.setBackgroundColor(ColorUtils.setAlphaComponent(backGroundColor, 245))
bottomNavigationView.setOnNavigationItemSelectedListener { item ->
if (item.itemId == bottomNavigationView.selectedItemId)
return@setOnNavigationItemSelectedListener false
NavigationUI.onNavDestinationSelected(item, navController)
true
}
navController.addOnDestinationChangedListener { _: NavController?, destination: NavDestination?, _: Bundle? ->
val menu: Menu = bottomNavigationView.menu
val size: Int = menu.size()
for (i in 0 until size) {
val item: MenuItem = menu.getItem(i)
if (matchDestination(destination, item.itemId)) {
item.isChecked = true
}
}
}
}
private fun attachDrawer() {
val headerView: View = B.navigation.getHeaderView(0)
headerView.let {
it.findViewById<ImageView>(R.id.img)?.load(R.mipmap.ic_launcher) {
transform(RoundedCorners(8))
}
it.findViewById<TextView>(R.id.txt_name)?.text = getString(R.string.app_name)
it.findViewById<TextView>(R.id.txt_email)?.text =
("v${BuildConfig.VERSION_NAME}.${BuildConfig.VERSION_CODE}")
}
B.navigation.setNavigationItemSelectedListener { item: MenuItem ->
when (item.itemId) {
R.id.menu_apps_games -> {
open(AppsGamesActivity::class.java)
}
R.id.menu_apps_sale -> {
open(AppSalesActivity::class.java)
}
R.id.menu_blacklist_manager -> {
open(BlacklistActivity::class.java)
}
R.id.menu_download_manager -> {
open(DownloadActivity::class.java)
}
R.id.menu_spoof_manager -> {
open(SpoofActivity::class.java)
}
R.id.menu_account_manager -> {
open(AccountActivity::class.java)
}
R.id.menu_settings -> {
open(SettingsActivity::class.java)
}
R.id.menu_about -> {
open(AboutActivity::class.java)
}
}
false
}
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp()
}
override fun onBackPressed() {
if (!navController.navigateUp()) {
if (lastBackPressed + 1000 > System.currentTimeMillis()) {
super.onBackPressed()
} else {
lastBackPressed = System.currentTimeMillis()
Toast.makeText(this, "Click twice to exit", Toast.LENGTH_SHORT).show()
}
}
}
override fun onConnected() {
hideNetworkConnectivitySheet()
}
override fun onDisconnected() {
showNetworkConnectivitySheet()
}
override fun onReconnected() {
}
private fun pickExternalFileDir() {
val getContentIntent = Intent(Intent.ACTION_GET_CONTENT)
getContentIntent.type = "*/*"
getContentIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
getContentIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true)
getContentIntent.addCategory(Intent.CATEGORY_OPENABLE)
startActivityForResult(
Intent.createChooser(
getContentIntent,
"Aurora Store - External Storage Access"
), 1337
)
}
}

View File

@@ -0,0 +1,36 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data
open class SingletonHolder<out T, in A>(private val constructor: (A) -> T) {
@Volatile
private var instance: T? = null
fun with(arg: A): T {
return when {
instance != null -> instance!!
else -> synchronized(this) {
if (instance == null) instance = constructor(arg)
instance!!
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data
sealed class ViewState {
object Loading : ViewState()
object Empty : ViewState()
data class Error(val error: String?) : ViewState()
data class Status(val status: String?) : ViewState()
data class Success<T>(val data: T) : ViewState()
}
sealed class RequestState {
object Init : RequestState()
object Pending : RequestState()
object Complete : RequestState()
}
sealed class AuthState {
object Available : AuthState()
object Unavailable : AuthState()
object SignedIn : AuthState()
object SignedOut : AuthState()
object Valid : AuthState()
data class Status(val status: String?) : AuthState()
}

View File

@@ -0,0 +1,90 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.downloader
import android.content.Context
import com.aurora.Constants
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Preferences
import com.tonyodev.fetch2.*
import com.tonyodev.fetch2core.DefaultStorageResolver
import com.tonyodev.fetch2core.getFileTempDir
class DownloadManager private constructor(var context: Context) {
companion object : SingletonHolder<DownloadManager, Context>(::DownloadManager)
var fetch: Fetch
init {
fetch = Fetch.getInstance(getFetchConfiguration(context))
}
fun getFetchInstance(): Fetch {
return fetch
}
private fun getFetchConfiguration(context: Context): FetchConfiguration {
var maxActive = Preferences.getInteger(context, Preferences.PREFERENCE_DOWNLOAD_ACTIVE)
if (maxActive == 0)
maxActive = 1
return FetchConfiguration.Builder(context)
.setDownloadConcurrentLimit(maxActive)
.enableLogging(BuildConfig.DEBUG)
.enableHashCheck(true)
.enableFileExistChecks(true)
.enableRetryOnNetworkGain(true)
.enableAutoStart(true)
.setInternetAccessUrlCheck(Constants.CONNECTIVITY_CHECK_URL)
.setAutoRetryMaxAttempts(3)
.setProgressReportingInterval(3000)
.setNamespace(BuildConfig.APPLICATION_ID)
.setStorageResolver(DefaultStorageResolver(context, getFileTempDir(context)))
.build()
}
fun isDownloading(fetchGroup: FetchGroup): Boolean {
return fetchGroup.downloadingDownloads.isNotEmpty()
|| fetchGroup.queuedDownloads.isNotEmpty()
|| fetchGroup.addedDownloads.isNotEmpty()
}
fun isCanceled(fetchGroup: FetchGroup): Boolean {
return fetchGroup.cancelledDownloads.isNotEmpty()
|| fetchGroup.removedDownloads.isNotEmpty()
|| fetchGroup.deletedDownloads.isNotEmpty()
}
fun updateOngoingDownloads(
fetch: Fetch, packageList: MutableList<String?>, download: Download,
fetchListener: FetchListener?
) {
if (packageList.contains(download.tag)) {
val packageName = download.tag
if (packageName != null) {
fetch.deleteGroup(packageName.hashCode())
packageList.remove(packageName)
}
}
if (packageList.size == 0) {
fetch.removeListener(fetchListener!!)
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.downloader
import android.content.Context
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.File
import com.aurora.store.util.PathUtil
import com.google.gson.GsonBuilder
import com.tonyodev.fetch2.EnqueueAction
import com.tonyodev.fetch2.NetworkType
import com.tonyodev.fetch2.Request
import com.tonyodev.fetch2core.Extras
import java.lang.reflect.Modifier
private inline fun Request.attachMetaData(app: App) {
apply {
groupId = app.id
tag = app.packageName
enqueueAction = EnqueueAction.UPDATE_ACCORDINGLY
networkType = NetworkType.ALL
}
}
private inline fun Request.attachExtra(app: App) {
val stringMap: MutableMap<String, String> = mutableMapOf()
val gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.create()
stringMap[Constants.STRING_EXTRA] = gson.toJson(app)
apply {
extras = Extras(stringMap)
}
}
object RequestBuilder {
fun buildRequest(context: Context, app: App, file: File): Request {
val fileName = when (file.type) {
File.FileType.BASE,
File.FileType.SPLIT -> PathUtil.getApkDownloadFile(context, app, file)
File.FileType.OBB,
File.FileType.PATCH -> PathUtil.getObbDownloadFile(context, app, file)
}
return Request(file.url, fileName).apply {
attachMetaData(app)
attachExtra(app)
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.event
sealed class BusEvent {
data class InstallEvent(var packageName: String, var error: String = String()) : BusEvent()
data class UninstallEvent(var packageName: String, var error: String = String()) : BusEvent()
data class Blacklisted(var packageName: String, var error: String = String()) : BusEvent()
data class GoogleAAS(
var success: Boolean,
var email: String = String(),
var aasToken: String = String()
) : BusEvent()
}

View File

@@ -0,0 +1,49 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.installer
import android.content.Context
import android.os.Build
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
open class AppInstaller private constructor(var context: Context) {
companion object : SingletonHolder<AppInstaller, Context>(::AppInstaller)
fun getPreferredInstaller(): IInstaller {
val prefValue = Preferences.getInteger(
context,
PREFERENCE_INSTALLER_ID
)
return when (prefValue) {
1 -> NativeInstaller(context)
2 -> RootInstaller(context)
3 -> ServiceInstaller(context)
else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
SessionInstaller(context)
} else {
NativeInstaller(context)
}
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.installer
interface IInstaller {
fun install(packageName: String, files: List<Any>)
fun uninstall(packageName: String)
fun clearQueue()
fun isAlreadyQueued(packageName: String): Boolean
fun removeFromInstallQueue(packageName: String)
}

View File

@@ -0,0 +1,70 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.installer
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.content.FileProvider
import com.aurora.store.BuildConfig
import java.io.File
abstract class InstallerBase(protected var context: Context) : IInstaller {
private val enqueuedInstalls: MutableSet<String> = mutableSetOf()
override fun clearQueue() {
enqueuedInstalls.clear()
}
override fun isAlreadyQueued(packageName: String): Boolean {
return enqueuedInstalls.contains(packageName)
}
override fun removeFromInstallQueue(packageName: String) {
enqueuedInstalls.remove(packageName)
}
override fun uninstall(packageName: String) {
val uri = Uri.fromParts("package", packageName, null)
val intent = Intent().apply {
data = uri
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
intent.action = Intent.ACTION_DELETE
} else {
intent.action = Intent.ACTION_UNINSTALL_PACKAGE
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
}
context.startActivity(intent)
}
open fun getUri(file: File): Uri {
return FileProvider.getUriForFile(
context,
"${BuildConfig.APPLICATION_ID}.fileProvider",
file
)
}
}

View File

@@ -0,0 +1,76 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.installer
import android.app.Service
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import org.apache.commons.lang3.StringUtils
class InstallerService : Service() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
//Send broadcast for the installation status of the package
sendStatusBroadcast(status, packageName)
//Launch user confirmation activity
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
confirmationIntent!!.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
confirmationIntent.putExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME,
"com.android.vending"
)
confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
startActivity(confirmationIntent)
} catch (e: Exception) {
sendStatusBroadcast(PackageInstaller.STATUS_FAILURE, packageName)
}
}
stopSelf()
return START_NOT_STICKY
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private fun sendStatusBroadcast(status: Int, packageName: String?) {
if (StringUtils.isNotEmpty(packageName)) {
val statusIntent = Intent(ACTION_SESSION_INSTALLER)
statusIntent.putExtra(PackageInstaller.EXTRA_STATUS, status)
statusIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName)
sendBroadcast(statusIntent)
}
}
override fun onBind(intent: Intent): IBinder? {
return null
}
companion object {
private const val ACTION_SESSION_INSTALLER = "ACTION_SESSION_INSTALLER"
}
}

View File

@@ -0,0 +1,66 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.installer
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import com.aurora.store.util.Log
import java.io.File
class NativeInstaller(context: Context) : InstallerBase(context) {
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
} else {
files.map {
when (it) {
is File -> it
is String -> File(it)
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}.forEach {
xInstall(packageName, it)
}
}
}
private fun xInstall(packageName: String, file: File) {
val intent: Intent
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
intent.data = getUri(file)
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
} else {
intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
context.startActivity(intent)
}
}

View File

@@ -0,0 +1,99 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.installer
import android.content.Context
import com.aurora.store.R
import com.aurora.store.util.Log
import com.aurora.store.util.extensions.isLAndAbove
import com.aurora.store.util.extensions.toast
import com.topjohnwu.superuser.Shell
import java.io.File
import java.util.regex.Pattern
class RootInstaller(context: Context) : InstallerBase(context) {
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
} else {
if (Shell.getShell().isRoot) {
files.map {
when (it) {
is File -> it
is String -> File(it)
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}.let {
if (isLAndAbove())
xInstall(packageName, it)
else
xInstallLegacy(packageName, it)
}
} else {
context.toast(context.getString(R.string.installer_root_unavailable))
Log.e(" >>>>>>>>>>>>>>>>>>>>>>>>>> NO ROOT ACCESS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
}
}
}
private fun xInstall(packageName: String, files: List<File>) {
var totalSize = 0
for (file in files)
totalSize += file.length().toInt()
val result: Shell.Result =
Shell.su("pm install-create -i com.android.vending --user 0 -r -S $totalSize")
.exec()
val response = result.out
val sessionIdPattern = Pattern.compile("(\\d+)")
val sessionIdMatcher = sessionIdPattern.matcher(response[0])
val found = sessionIdMatcher.find()
if (found) {
val sessionId = sessionIdMatcher.group(1).toInt()
if (Shell.getShell().isRoot) {
for (file in files) {
Shell.su("cat \"${file.absoluteFile}\" | pm install-write -S ${file.length()} $sessionId \"${file.name}\"")
.exec()
}
Shell.su("pm install-commit $sessionId").exec()
} else {
removeFromInstallQueue(packageName)
}
} else {
removeFromInstallQueue(packageName)
}
}
private fun xInstallLegacy(packageName: String, files: List<File>) {
if (Shell.getShell().isRoot) {
Shell.su("pm install -i com.android.vending --user 0 \"${files[0].name}\"").exec()
} else {
removeFromInstallQueue(packageName)
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.installer
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.os.RemoteException
import androidx.annotation.RequiresApi
import com.aurora.services.IPrivilegedCallback
import com.aurora.services.IPrivilegedService
import com.aurora.store.BuildConfig
import com.aurora.store.util.Log
import java.io.File
class ServiceInstaller(context: Context) : InstallerBase(context) {
companion object {
const val ACTION_INSTALL_REPLACE_EXISTING = 2
const val PRIVILEGED_EXTENSION_PACKAGE_NAME = "com.aurora.services"
const val PRIVILEGED_EXTENSION_SERVICE_INTENT = "com.aurora.services.IPrivilegedService"
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
} else {
Log.i("Received service install request for $packageName")
val uriList = files.map {
when (it) {
is File -> getUri(it)
is String -> getUri(File(it))
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}
xInstall(packageName, uriList)
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun xInstall(packageName: String, uriList: List<Uri>) {
val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
val service = IPrivilegedService.Stub.asInterface(binder)
val callback = object : IPrivilegedCallback.Stub() {
override fun handleResult(packageName: String, returnCode: Int) {
removeFromInstallQueue(packageName)
}
}
try {
service.installSplitPackage(
packageName,
uriList,
ACTION_INSTALL_REPLACE_EXISTING,
BuildConfig.APPLICATION_ID,
callback
)
} catch (e: RemoteException) {
removeFromInstallQueue(packageName)
Log.e("Failed to connect Aurora Services")
}
}
override fun onServiceDisconnected(name: ComponentName) {
removeFromInstallQueue(packageName)
Log.e("Disconnected from Aurora Services")
}
}
val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT)
intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME)
context.bindService(
intent,
serviceConnection,
Context.BIND_AUTO_CREATE
)
}
}

View File

@@ -0,0 +1,119 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.installer
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller.SessionParams
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import com.aurora.store.BuildConfig
import com.aurora.store.util.Log
import org.apache.commons.io.IOUtils
import java.io.File
class SessionInstaller(context: Context) : InstallerBase(context) {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
} else {
Log.i("Received service install request for $packageName")
val uriList = files.map {
when (it) {
is File -> getUri(it)
is String -> getUri(File(it))
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}
xInstall(packageName, uriList)
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun xInstall(packageName: String, uriList: List<Uri>) {
val packageInstaller = context.packageManager.packageInstaller
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(sessionParams)
val session = packageInstaller.openSession(sessionId)
try {
Log.i("Writing splits to session for $packageName")
var apkId = 1
for (uri in uriList) {
val inputStream = context.contentResolver.openInputStream(uri)
val outputStream = session.openWrite(
"${packageName}_${apkId++}",
0,
-1
)
IOUtils.copy(inputStream, outputStream)
session.fsync(outputStream)
IOUtils.close(inputStream)
IOUtils.close(outputStream)
}
val intent = Intent(context, InstallerService::class.java)
val pendingIntent = PendingIntent.getService(
context,
sessionId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
Log.i("Starting install session for $packageName")
session.commit(pendingIntent.intentSender)
session.close()
} catch (e: Exception) {
session.abandon()
removeFromInstallQueue(packageName)
Log.e("Failed to install $packageName : %s", e.message)
}
}
override fun getUri(file: File): Uri {
val uri = FileProvider.getUriForFile(
context,
"${BuildConfig.APPLICATION_ID}.fileProvider",
file
)
uri.apply {
context.grantUriPermission(
BuildConfig.APPLICATION_ID,
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
return uri
}
}

View File

@@ -0,0 +1,36 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
data class Accent(
var id: Int,
var accent: String
) {
override fun equals(other: Any?): Boolean {
return when (other) {
is Accent -> other.id == id
else -> false
}
}
override fun hashCode(): Int {
return id.hashCode()
}
}

View File

@@ -0,0 +1,40 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
import android.graphics.drawable.Drawable
data class Black(val packageName: String) {
var displayName: String = String()
var drawable: Drawable? = null
var versionName: String = String()
var versionCode: Int = 0
override fun hashCode(): Int {
return packageName.hashCode()
}
override fun equals(other: Any?): Boolean {
return when (other) {
is Black -> other.packageName == packageName
else -> false
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
data class Dash(
var id: Int,
var title: String,
var subtitle: String,
var icon: String,
var url: String
) {
override fun equals(other: Any?): Boolean {
return when (other) {
is Dash -> other.id == id
else -> false
}
}
override fun hashCode(): Int {
return id.hashCode()
}
}

View File

@@ -0,0 +1,36 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
import com.tonyodev.fetch2.Download
data class DownloadFile(val download: Download) {
override fun hashCode(): Int {
return download.id
}
override fun equals(other: Any?): Boolean {
return when (other) {
is DownloadFile -> other.download.status == download.status && other.download.progress == download.progress
else -> false
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
import java.text.DateFormat
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
class ExodusReport {
val creator: String = String()
val name: String = String()
val reports: List<Report> = listOf()
}
class Report {
val id: Int = 0
val downloads: String = String()
val version: String = String()
val creationDate: String = String()
val updatedAt: String = String()
val versionCode: String = String()
val trackers: List<Int> = listOf()
fun getFormattedCreationDate(): String {
return try {
val simpleDateFormat: DateFormat = SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
Locale.getDefault()
)
simpleDateFormat.parse(creationDate).toString()
} catch (e: ParseException) {
""
}
}
}
class ExodusTracker {
var id: Int = 0
var name: String = String()
var url: String = String()
var signature: String = String()
var date: String = String()
override fun hashCode(): Int {
return id
}
override fun equals(other: Any?): Boolean {
return when (other) {
is ExodusTracker -> other.id == id
else -> false
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
data class Installer(
var id: Int,
var title: String,
var subtitle: String,
var description: String,
var url: String
) {
override fun equals(other: Any?): Boolean {
return when (other) {
is Installer -> other.id == id
else -> false
}
}
override fun hashCode(): Int {
return id.hashCode()
}
}

View File

@@ -0,0 +1,39 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
data class Link(
var id: Int,
var title: String,
var subtitle: String,
var url: String,
var icon: Int,
) {
override fun equals(other: Any?): Boolean {
return when (other) {
is Link -> other.id == id
else -> false
}
}
override fun hashCode(): Int {
return id.hashCode()
}
}

View File

@@ -0,0 +1,37 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.model
data class Theme(
var id: Int,
var title: String,
var subtitle: String
) {
override fun equals(other: Any?): Boolean {
return when (other) {
is Theme -> other.id == id
else -> false
}
}
override fun hashCode(): Int {
return id.hashCode()
}
}

View File

@@ -0,0 +1,126 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.network
import com.aurora.gplayapi.GooglePlayApi
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.gplayapi.network.IHttpClient
import com.aurora.store.BuildConfig
import com.aurora.store.util.Log
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.*
import java.nio.charset.Charset
object FuelClient : IHttpClient {
override fun get(url: String, headers: Map<String, String>): PlayResponse {
return get(url, headers, hashMapOf())
}
override fun get(
url: String,
headers: Map<String, String>,
params: Map<String, String>
): PlayResponse {
val parameters = params
.map { it.key to it.value }
.toList()
val (request, response, result) = Fuel.get(url, parameters)
.header(headers)
.response()
return buildPlayResponse(response, request)
}
override fun getAuth(url: String): PlayResponse {
val (request, response, result) = Fuel.get(url)
.appendHeader(
"User-Agent",
"${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}"
)
.response()
return buildPlayResponse(response, request)
}
override fun get(
url: String,
headers: Map<String, String>,
paramString: String
): PlayResponse {
val (request, response, result) = Fuel.get(url + paramString)
.header(headers)
.response()
return buildPlayResponse(response, request)
}
override fun post(url: String, headers: Map<String, String>, body: ByteArray): PlayResponse {
val (request, response, result) = Fuel.post(url)
.header(headers)
.appendHeader(Headers.CONTENT_TYPE, "application/x-protobuf")
.body(body, Charset.defaultCharset())
.response()
return buildPlayResponse(response, request)
}
override fun post(
url: String,
headers: Map<String, String>,
params: Map<String, String>
): PlayResponse {
val parameters = params
.map { it.key to it.value }
.toList()
val (request, response, result) = Fuel.post(url, parameters)
.header(headers)
.response()
return buildPlayResponse(response, request)
}
override fun postAuth(url: String, body: ByteArray): PlayResponse {
val (request, response, result) = Fuel.post(url)
.appendHeader(
"User-Agent",
"${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}"
)
.body(body)
.response()
return buildPlayResponse(response, request)
}
@JvmStatic
private fun buildPlayResponse(response: Response, request: Request): PlayResponse {
return PlayResponse().apply {
isSuccessful = response.isSuccessful
code = response.statusCode
GooglePlayApi
if (response.isSuccessful) {
responseBytes = response.body().toByteArray()
}
if (response.isClientError || response.isServerError) {
errorBytes = response.responseMessage.toByteArray()
errorString = String(errorBytes)
}
}.also {
Log.i("FUEL [${request.method}:${response.statusCode}] ${response.url}")
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.network
import android.os.Build
import com.aurora.gplayapi.network.IHttpClient
object HttpClient {
fun getPreferredClient(): IHttpClient {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
OkHttpClient
} else {
FuelClient
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.network
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.gplayapi.network.IHttpClient
import com.aurora.store.BuildConfig
import com.aurora.store.util.Log
import okhttp3.*
import okhttp3.Headers.Companion.toHeaders
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
import java.util.concurrent.TimeUnit
object OkHttpClient : IHttpClient {
private const val POST = "POST"
private const val GET = "GET"
private val okHttpClient = OkHttpClient().newBuilder()
.connectTimeout(20, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.followRedirects(true)
.followSslRedirects(true)
.build()
@Throws(IOException::class)
fun post(url: String, headers: Map<String, String>, requestBody: RequestBody): PlayResponse {
val request = Request.Builder()
.url(url)
.headers(headers.toHeaders())
.method(POST, requestBody)
.build()
return processRequest(request)
}
@Throws(IOException::class)
override fun post(
url: String,
headers: Map<String, String>,
params: Map<String, String>
): PlayResponse {
val request = Request.Builder()
.url(buildUrl(url, params))
.headers(headers.toHeaders())
.method(POST, "".toRequestBody(null))
.build()
return processRequest(request)
}
override fun postAuth(url: String, body: ByteArray): PlayResponse {
val requestBody = body.toRequestBody("application/json".toMediaType(), 0, body.size)
val request = Request.Builder()
.url(url)
.header(
"User-Agent",
"${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}"
)
.method(POST, requestBody)
.build()
return processRequest(request)
}
@Throws(IOException::class)
override fun post(url: String, headers: Map<String, String>, body: ByteArray): PlayResponse {
val requestBody = body.toRequestBody(
"application/x-protobuf".toMediaType(),
0,
body.size
)
return post(url, headers, requestBody)
}
@Throws(IOException::class)
override fun get(url: String, headers: Map<String, String>): PlayResponse {
return get(url, headers, mapOf())
}
@Throws(IOException::class)
override fun get(
url: String,
headers: Map<String, String>,
params: Map<String, String>
): PlayResponse {
val request = Request.Builder()
.url(buildUrl(url, params))
.headers(headers.toHeaders())
.method(GET, null)
.build()
return processRequest(request)
}
override fun getAuth(url: String): PlayResponse {
val request = Request.Builder()
.url(url)
.header(
"User-Agent",
"${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}"
)
.method(GET, null)
.build()
return processRequest(request)
}
@Throws(IOException::class)
override fun get(
url: String,
headers: Map<String, String>,
paramString: String
): PlayResponse {
val request = Request.Builder()
.url(url + paramString)
.headers(headers.toHeaders())
.method(GET, null)
.build()
return processRequest(request)
}
private fun processRequest(request: Request): PlayResponse {
val call = okHttpClient.newCall(request)
return buildPlayResponse(call.execute())
}
private fun buildUrl(url: String, params: Map<String, String>): HttpUrl {
val urlBuilder = url.toHttpUrl().newBuilder()
params.forEach {
urlBuilder.addQueryParameter(it.key, it.value)
}
return urlBuilder.build()
}
private fun buildPlayResponse(response: Response): PlayResponse {
return PlayResponse().apply {
isSuccessful = response.isSuccessful
code = response.code
if (response.body != null) {
responseBytes = response.body!!.bytes()
}
if (!isSuccessful) {
errorString = response.message
}
}.also {
Log.i("OKHTTP [${response.code}] ${response.request.url}")
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.content.Context
import com.aurora.Constants
import com.aurora.store.AccountType
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Preferences
class AccountProvider private constructor(var context: Context) {
companion object : SingletonHolder<AccountProvider, Context>(::AccountProvider)
fun isSignedIn(): Boolean {
return Preferences.getBoolean(context, Constants.ACCOUNT_SIGNED_IN)
}
fun getSignInTimeStamp(): Long {
return Preferences.getLong(context, Constants.ACCOUNT_SIGNED_TIMESTAMP)
}
fun getAccountType(): AccountType {
val rawType = Preferences.getString(context, Constants.ACCOUNT_TYPE)
return when (rawType) {
"GOOGLE" -> AccountType.GOOGLE
else -> AccountType.ANONYMOUS
}
}
fun logout() {
Preferences.putBoolean(context, Constants.ACCOUNT_SIGNED_IN, false)
Preferences.putString(context, Constants.ACCOUNT_EMAIL_PLAIN, "")
Preferences.putString(context, Constants.ACCOUNT_AAS_PLAIN, "")
}
}

View File

@@ -0,0 +1,26 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import androidx.core.content.FileProvider
class ApkProvider : FileProvider() {
}

View File

@@ -0,0 +1,53 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.content.Context
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Log
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_AUTH_DATA
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.lang.reflect.Modifier
class AuthProvider private constructor(var context: Context) {
companion object : SingletonHolder<AuthProvider, Context>(::AuthProvider)
private var gson: Gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.create()
fun getAuthData(): AuthData {
return getSavedAuthData()
}
private fun getSavedAuthData(): AuthData {
Log.i("Loading saved AuthData")
val rawAuth: String = Preferences.getString(context, PREFERENCE_AUTH_DATA)
return if (rawAuth.isNotEmpty())
gson.fromJson(rawAuth, AuthData::class.java)
else
AuthData("", "")
}
}

View File

@@ -0,0 +1,78 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.content.Context
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Preferences
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Modifier
class BlacklistProvider private constructor(var context: Context) {
companion object : SingletonHolder<BlacklistProvider, Context>(::BlacklistProvider) {
const val PREFERENCE_BLACKLIST = "PREFERENCE_BLACKLIST"
}
private var gson: Gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.create()
fun getBlackList(): MutableSet<String> {
val rawBlacklist = Preferences.getString(context, PREFERENCE_BLACKLIST)
return try {
if (rawBlacklist.isEmpty())
mutableSetOf()
else
gson.fromJson(rawBlacklist, object : TypeToken<Set<String?>?>() {}.type)
} catch (e: Exception) {
mutableSetOf()
}
}
fun isBlacklisted(packageName: String): Boolean {
return getBlackList().contains(packageName)
}
fun blacklist(packageName: String) {
val oldBlackList: MutableSet<String> = getBlackList()
oldBlackList.add(packageName)
save(oldBlackList)
}
fun whitelist(packageName: String) {
val oldBlackList: MutableSet<String> = getBlackList()
oldBlackList.remove(packageName)
save(oldBlackList)
}
fun blacklist(packageNames: Set<String>) {
val oldBlackList: MutableSet<String> = getBlackList()
oldBlackList.addAll(packageNames)
save(oldBlackList)
}
@Synchronized
fun save(blacklist: Set<String>) {
Preferences.putString(context, PREFERENCE_BLACKLIST, gson.toJson(blacklist))
}
}

View File

@@ -0,0 +1,131 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.opengl.GLES10
import android.text.TextUtils
import java.util.*
import javax.microedition.khronos.egl.EGL10
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.egl.EGLContext
import javax.microedition.khronos.egl.EGLDisplay
object EglExtensionProvider {
@JvmStatic
val eglExtensions: List<String>
get() {
val glExtensions: MutableSet<String> = HashSet()
val egl10 = EGLContext.getEGL() as EGL10
val display = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
egl10.eglInitialize(display, IntArray(2))
val cf = IntArray(1)
if (egl10.eglGetConfigs(display, null, 0, cf)) {
val configs = arrayOfNulls<EGLConfig>(cf[0])
if (egl10.eglGetConfigs(display, configs, cf[0], cf)) {
val a1 = intArrayOf(
EGL10.EGL_WIDTH,
EGL10.EGL_PBUFFER_BIT,
EGL10.EGL_HEIGHT,
EGL10.EGL_PBUFFER_BIT,
EGL10.EGL_NONE
)
val a2 = intArrayOf(12440, EGL10.EGL_PIXMAP_BIT, EGL10.EGL_NONE)
val a3 = IntArray(1)
for (i in 0 until cf[0]) {
egl10.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, a3)
if (a3[0] != EGL10.EGL_SLOW_CONFIG) {
egl10.eglGetConfigAttrib(
display,
configs[i],
EGL10.EGL_SURFACE_TYPE,
a3
)
if (1 and a3[0] != 0) {
egl10.eglGetConfigAttrib(
display,
configs[i],
EGL10.EGL_RENDERABLE_TYPE,
a3
)
if (1 and a3[0] != 0) {
addExtensionsForConfig(
egl10,
display,
configs[i],
a1,
null,
glExtensions
)
}
if (4 and a3[0] != 0) {
addExtensionsForConfig(
egl10,
display,
configs[i],
a1,
a2,
glExtensions
)
}
}
}
}
}
}
egl10.eglTerminate(display)
val sorted: List<String> = ArrayList(glExtensions)
Collections.sort(sorted)
return sorted
}
private fun addExtensionsForConfig(
egl10: EGL10,
eglDisplay: EGLDisplay,
eglConfig: EGLConfig?,
ai: IntArray,
ai1: IntArray?,
set: MutableSet<String>
) {
val eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, ai1)
if (eglContext === EGL10.EGL_NO_CONTEXT) {
return
}
val eglSurface = egl10.eglCreatePbufferSurface(eglDisplay, eglConfig, ai)
if (eglSurface === EGL10.EGL_NO_SURFACE) {
egl10.eglDestroyContext(eglDisplay, eglContext)
} else {
egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)
val s = GLES10.glGetString(7939)
if (!TextUtils.isEmpty(s)) {
val `as` = s.split(" ".toRegex()).toTypedArray()
val i = `as`.size
set.addAll(listOf(*`as`).subList(0, i))
}
egl10.eglMakeCurrent(
eglDisplay,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT
)
egl10.eglDestroySurface(eglDisplay, eglSurface)
egl10.eglDestroyContext(eglDisplay, eglContext)
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.content.Context
import com.aurora.store.data.SingletonHolder
import org.json.JSONArray
import org.json.JSONObject
import java.nio.charset.StandardCharsets
class ExodusDataProvider private constructor(val context: Context) {
companion object : SingletonHolder<ExodusDataProvider, Context>(::ExodusDataProvider)
private val exodusTrackers: JSONObject
init {
exodusTrackers = loadLocalTrackers()
}
fun getLocalTrackers(): JSONObject {
return exodusTrackers
}
fun getFilteredTrackers(trackerIds: List<Int>): List<JSONObject> {
return trackerIds.map {
exodusTrackers.getJSONObject(
it.toString()
)
}.toList()
}
private fun loadLocalTrackers(): JSONObject {
val inputStream = context.assets.open("exodus_trackers.json")
val bytes = ByteArray(inputStream.available())
inputStream.read(bytes)
inputStream.close()
val json = String(bytes, StandardCharsets.UTF_8)
val jsonArray = JSONArray(json)
return jsonArray.getJSONObject(0)
}
}

View File

@@ -0,0 +1,167 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.app.ActivityManager
import android.content.Context
import android.content.ContextWrapper
import android.content.res.Configuration
import android.os.Build
import android.text.TextUtils
import java.util.*
class NativeDeviceInfoProvider(context: Context) : ContextWrapper(context) {
fun getNativeDeviceProperties(): Properties {
return Properties().apply {
//Build Props
setProperty("UserReadableName", Build.DEVICE)
setProperty("Build.HARDWARE", Build.HARDWARE)
setProperty(
"Build.RADIO",
if (Build.getRadioVersion() != null)
Build.getRadioVersion()
else
"unknown"
)
setProperty("Build.FINGERPRINT", Build.FINGERPRINT)
setProperty("Build.BRAND", Build.BRAND)
setProperty("Build.DEVICE", Build.DEVICE)
setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}")
setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE)
setProperty("Build.MODEL", Build.MODEL)
setProperty("Build.MANUFACTURER", Build.MANUFACTURER)
setProperty("Build.PRODUCT", Build.PRODUCT)
setProperty("Build.ID", Build.ID)
setProperty("Build.BOOTLOADER", Build.BOOTLOADER)
val config = applicationContext.resources.configuration
setProperty("TouchScreen", "${config.touchscreen}")
setProperty("Keyboard", "${config.keyboard}")
setProperty("Navigation", "${config.navigation}")
setProperty("ScreenLayout", "${config.screenLayout and 15}")
setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}")
setProperty(
"HasFiveWayNavigation",
"${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}"
)
//Display Metrics
val metrics = applicationContext.resources.displayMetrics
setProperty("Screen.Density", "${metrics.densityDpi}")
setProperty("Screen.Width", "${metrics.widthPixels}")
setProperty("Screen.Height", "${metrics.heightPixels}")
//Supported Platforms
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setProperty("Platforms", Build.SUPPORTED_ABIS.joinToString(separator = ","))
} else {
val platform = mutableListOf<String>()
if (!TextUtils.isEmpty(Build.CPU_ABI)) {
platform.add(Build.CPU_ABI)
}
if (!TextUtils.isEmpty(Build.CPU_ABI2)) {
platform.add(Build.CPU_ABI2)
}
setProperty("Platforms", platform.joinToString(separator = ","))
}
//Supported Features
setProperty("Features", getFeatures().joinToString(separator = ","))
//Shared Locales
setProperty("Locales", getLocales().joinToString(separator = ","))
//Shared Libraries
setProperty("SharedLibraries", getSharedLibraries().joinToString(separator = ","))
//GL Extensions
val activityManager =
applicationContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager
setProperty(
"GL.Version",
activityManager.deviceConfigurationInfo.reqGlEsVersion.toString()
)
setProperty(
"GL.Extensions",
EglExtensionProvider.eglExtensions.joinToString(separator = ",")
)
//Google Related Props
val gsfVersionProvider = NativeGsfVersionProvider(applicationContext)
setProperty("Client", "android-google")
setProperty("GSF.version", "${gsfVersionProvider.getGsfVersionCode(true)}")
setProperty("Vending.version", "${gsfVersionProvider.getVendingVersionCode(true)}")
setProperty("Vending.versionString", gsfVersionProvider.getVendingVersionString(true))
//MISC
setProperty("Roaming", "mobile-notroaming")
setProperty("TimeZone", "UTC-10")
//Telephony (USA 3650 AT&T)
setProperty("CellOperator", "310")
setProperty("SimOperator", "38")
}
}
private fun getFeatures(): List<String> {
val featureStringList: MutableList<String> = ArrayList()
try {
val availableFeatures = applicationContext.packageManager.systemAvailableFeatures
for (feature in availableFeatures) {
if (feature.name.isNotEmpty()) {
featureStringList.add(feature.name)
}
}
} catch (e: Exception) {
}
return featureStringList
}
private fun getLocales(): List<String> {
val localeList: MutableList<String> = ArrayList()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
localeList.addAll(listOf(*applicationContext.assets.locales))
} else {
for (locale in Locale.getAvailableLocales()) {
localeList.add(locale.toString())
}
}
val locales: MutableList<String> = ArrayList()
for (locale in localeList) {
if (TextUtils.isEmpty(locale)) {
continue
}
locales.add(locale.replace("-", "_"))
}
return locales
}
private fun getSharedLibraries(): List<String> {
val systemSharedLibraryNames = applicationContext.packageManager.systemSharedLibraryNames
val libraries: MutableList<String> = ArrayList()
try {
if (systemSharedLibraryNames != null) {
libraries.addAll(listOf(*systemSharedLibraryNames))
}
} catch (e: Exception) {
}
return libraries
}
}

View File

@@ -0,0 +1,90 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.content.Context
import android.content.pm.PackageManager
class NativeGsfVersionProvider(context: Context) {
private var gsfVersionCode = 0
private var vendingVersionCode = 0
private var vendingVersionString = ""
init {
try {
gsfVersionCode =
context.packageManager.getPackageInfo(GOOGLE_SERVICES_PACKAGE_ID, 0).versionCode
} catch (e: PackageManager.NameNotFoundException) {
// com.google.android.gms not found
}
try {
val packageInfo = context.packageManager.getPackageInfo(GOOGLE_VENDING_PACKAGE_ID, 0)
vendingVersionCode = packageInfo.versionCode
vendingVersionString = packageInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
// com.android.vending not found
}
}
init {
try {
gsfVersionCode =
context.packageManager.getPackageInfo(GOOGLE_SERVICES_PACKAGE_ID, 0).versionCode
} catch (e: PackageManager.NameNotFoundException) {
// com.google.android.gms not found
}
try {
val packageInfo = context.packageManager.getPackageInfo(GOOGLE_VENDING_PACKAGE_ID, 0)
vendingVersionCode = packageInfo.versionCode
vendingVersionString = packageInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
// com.android.vending not found
}
}
fun getGsfVersionCode(defaultIfNotFound: Boolean): Int {
return if (defaultIfNotFound && gsfVersionCode < GOOGLE_SERVICES_VERSION_CODE)
GOOGLE_SERVICES_VERSION_CODE
else
gsfVersionCode
}
fun getVendingVersionCode(defaultIfNotFound: Boolean): Int {
return if (defaultIfNotFound && vendingVersionCode < GOOGLE_VENDING_VERSION_CODE)
GOOGLE_VENDING_VERSION_CODE
else
vendingVersionCode
}
fun getVendingVersionString(defaultIfNotFound: Boolean): String {
return if (defaultIfNotFound && vendingVersionCode < GOOGLE_VENDING_VERSION_CODE)
GOOGLE_VENDING_VERSION_STRING
else
vendingVersionString
}
companion object {
private const val GOOGLE_SERVICES_PACKAGE_ID = "com.google.android.gms"
private const val GOOGLE_VENDING_PACKAGE_ID = "com.android.vending"
private const val GOOGLE_SERVICES_VERSION_CODE = 203019037
private const val GOOGLE_VENDING_VERSION_CODE = 82151710
private const val GOOGLE_VENDING_VERSION_STRING = "21.5.17-21 [0] [PR] 326734551"
}
}

View File

@@ -0,0 +1,101 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.content.Context
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Log
import com.novoda.merlin.Merlin
class NetworkProvider(var context: Context) {
companion object : SingletonHolder<NetworkProvider, Context>(::NetworkProvider) {
private var networkListeners: MutableList<NetworkListener> = mutableListOf()
fun addListener(networkListener: NetworkListener) {
Log.i("Network-Provider added to ${networkListener.javaClass.simpleName}")
networkListeners.add(networkListener)
}
fun removeListener(networkListener: NetworkListener) {
Log.i("Network-Provider removed from ${networkListener.javaClass.simpleName}")
networkListeners.remove(networkListener)
}
}
private var merlin: Merlin = Merlin.Builder()
.withAllCallbacks()
.build(context)
private var isDisconnected = true
fun bind() {
merlin.bind()
merlin.registerConnectable {
if (isDisconnected) {
isDisconnected = false
onReConnected()
} else {
onConnected()
}
}
merlin.registerDisconnectable {
isDisconnected = true
onDisconnected()
}
}
fun unbind() {
networkListeners.clear()
merlin.unbind()
Log.i("Network-Provider destroyed")
}
private fun onConnected() {
Log.i("Network-Provider connected")
isDisconnected = false
networkListeners.forEach {
it.onConnected()
}
}
private fun onReConnected() {
Log.i("Network-Provider reconnected")
networkListeners.forEach {
it.onReconnected()
}
}
private fun onDisconnected() {
Log.e("Network-Provider disconnected")
networkListeners.forEach {
it.onDisconnected()
}
}
interface NetworkListener {
fun onConnected()
fun onDisconnected()
fun onReconnected()
}
}

View File

@@ -0,0 +1,176 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.content.Context
import com.aurora.store.BuildConfig
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Log
import com.aurora.store.util.PathUtil
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.util.*
import java.util.jar.JarEntry
import java.util.jar.JarFile
class SpoofDeviceProvider private constructor(var context: Context) {
companion object : SingletonHolder<SpoofDeviceProvider, Context>(::SpoofDeviceProvider) {
private const val SUFFIX = ".properties"
fun filenameValid(filename: String): Boolean {
return filename.endsWith(SUFFIX)
}
}
val availableDevice: List<Properties>
get() {
val propertiesList: MutableList<Properties> = ArrayList()
propertiesList.add(0, NativeDeviceInfoProvider(context).getNativeDeviceProperties())
propertiesList.addAll(spoofDevicesFromApk)
propertiesList.addAll(spoofDevicesFromUser)
return propertiesList
}
private val spoofDevicesFromApk: List<Properties>
get() {
val jarFile = apkAsJar
val propertiesList: MutableList<Properties> = ArrayList()
if (null == jarFile) {
return propertiesList
}
val entries = jarFile.entries()
while (entries.hasMoreElements()) {
val entry = entries.nextElement()
if (!filenameValid(entry.name)) {
continue
}
propertiesList.add(getProperties(jarFile, entry))
}
return propertiesList
}
private val spoofDevicesFromUser: List<Properties>
get() {
val deviceNames: MutableList<Properties> = ArrayList()
val defaultDir = File(PathUtil.getExternalPath())
val files = defaultDir.listFiles()
if (defaultDir.exists() && files != null) {
for (file in files) {
if (!file.isFile || !filenameValid(file.name)) {
continue
}
deviceNames.add(getProperties(file))
}
}
return deviceNames
}
private fun getProperties(jarFile: JarFile, entry: JarEntry): Properties {
val properties = Properties()
try {
properties.load(jarFile.getInputStream(entry))
properties.setProperty("CONFIG_NAME", entry.name)
} catch (e: IOException) {
Log.e("Could not read %s", entry.name)
}
return properties
}
private fun getProperties(file: File): Properties {
val properties = Properties()
try {
properties.load(BufferedInputStream(FileInputStream(file)))
properties.setProperty("CONFIG_NAME", file.name)
} catch (e: IOException) {
Log.e("Could not read %s", file.name)
}
return properties
}
private val devicesFromApk: Map<String, String>
get() {
val deviceNames: MutableMap<String, String> = HashMap()
val jarFile = apkAsJar ?: return deviceNames
val entries = jarFile.entries()
while (entries.hasMoreElements()) {
val entry = entries.nextElement()
if (!filenameValid(entry.name)) {
continue
}
deviceNames[entry.name] =
getProperties(jarFile, entry).getProperty("UserReadableName")
}
return deviceNames
}
private val apkAsJar: JarFile?
get() {
val file = apkFile
try {
if (file != null && file.exists()) {
return JarFile(file)
}
} catch (e: IOException) {
Log.e("Could not open Aurora Store apk as a jar file")
}
return null
}
private val apkFile: File?
get() {
try {
val sourceDir: String = context.packageManager.getApplicationInfo(
BuildConfig.APPLICATION_ID,
0
).sourceDir
if (sourceDir.isNotEmpty()) {
return File(sourceDir)
}
} catch (ignored: Exception) {
}
return null
}
private val devicesFromDownloadDirectory: Map<String, String>
get() {
val deviceNames: MutableMap<String, String> = HashMap()
val defaultDir = File(PathUtil.getExternalPath())
if (!defaultDir.exists() || null == defaultDir.listFiles()) {
return deviceNames
}
for (file in defaultDir.listFiles()) {
if (!file.isFile || !filenameValid(file.name)) {
continue
}
val name = getProperties(file).getProperty("UserReadableName")
if (name != null) {
deviceNames[file.name] = name
}
}
return deviceNames
}
}

View File

@@ -0,0 +1,87 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.providers
import android.content.Context
import com.aurora.store.data.SingletonHolder
import com.aurora.store.util.Preferences
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.lang.reflect.Modifier
import java.util.*
class SpoofProvider private constructor(var context: Context) {
companion object : SingletonHolder<SpoofProvider, Context>(::SpoofProvider) {
const val LOCALE_SPOOF_ENABLED = "LOCALE_SPOOF_ENABLED"
const val LOCALE_SPOOF_LANG = "LOCALE_SPOOF_LANG"
const val LOCALE_SPOOF_COUNTRY = "LOCALE_SPOOF_COUNTRY"
const val DEVICE_SPOOF_ENABLED = "DEVICE_SPOOF_ENABLED"
const val DEVICE_SPOOF_PROPERTIES = "DEVICE_SPOOF_PROPERTIES"
}
fun isLocaleSpoofEnabled(): Boolean {
return Preferences.getBoolean(context, LOCALE_SPOOF_ENABLED)
}
fun isDeviceSpoofEnabled(): Boolean {
return Preferences.getBoolean(context, DEVICE_SPOOF_ENABLED)
}
fun getSpoofLocale(): Locale {
return if (isLocaleSpoofEnabled()) {
Locale(
Preferences.getString(context, LOCALE_SPOOF_LANG),
Preferences.getString(context, LOCALE_SPOOF_COUNTRY)
)
} else {
Locale.getDefault()
}
}
fun getSpoofDeviceProperties(): Properties {
return if (isDeviceSpoofEnabled()) {
val gson: Gson =
GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT)
.create()
return gson.fromJson(
Preferences.getString(context, DEVICE_SPOOF_PROPERTIES),
Properties::class.java
)
} else {
Properties()
}
}
fun setSpoofLocale(locale: Locale) {
Preferences.putBoolean(context, LOCALE_SPOOF_ENABLED, true)
Preferences.putString(context, LOCALE_SPOOF_LANG, locale.language)
Preferences.putString(context, LOCALE_SPOOF_COUNTRY, locale.country)
}
fun setSpoofDeviceProperties(properties: Properties) {
val gson: Gson =
GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT)
.create()
Preferences.putBoolean(context, DEVICE_SPOOF_ENABLED, true)
Preferences.putString(context, DEVICE_SPOOF_PROPERTIES, gson.toJson(properties))
}
}

View File

@@ -0,0 +1,27 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {}
}

View File

@@ -0,0 +1,38 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.aurora.Constants.FETCH_GROUP_ID
import com.aurora.store.data.downloader.DownloadManager
class DownloadCancelReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
if (extras != null) {
val groupId = extras.getInt(FETCH_GROUP_ID, -1)
DownloadManager
.with(context)
.getFetchInstance()
.cancelGroup(groupId)
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.aurora.Constants.FETCH_GROUP_ID
import com.aurora.store.data.downloader.DownloadManager
class DownloadPauseReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
if (extras != null) {
val groupId: Int = extras.getInt(FETCH_GROUP_ID, -1)
DownloadManager
.with(context)
.getFetchInstance()
.pauseGroup(groupId)
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.aurora.Constants.FETCH_GROUP_ID
import com.aurora.store.data.downloader.DownloadManager
class DownloadResumeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
if (extras != null) {
val groupId: Int = extras.getInt(FETCH_GROUP_ID, -1)
DownloadManager
.with(context)
.getFetchInstance()
.resumeGroup(groupId)
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class InstallReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
if (extras != null) {
/*val packageName = extras.getString(Constants.INTENT_PACKAGE_NAME, "")
val versionString = extras.getString(Constants.DOWNLOAD_VERSION_CODE)
if (!packageName.isEmpty() && versionString != null) {
AuroraApplication.getInstaller().installSplit(packageName, versionString.toInt())
}*/
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.aurora.store.data.event.BusEvent.InstallEvent
import com.aurora.store.data.event.BusEvent.UninstallEvent
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.util.PathUtil
import com.aurora.store.util.Preferences
import org.greenrobot.eventbus.EventBus
import java.io.File
open class PackageManagerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != null && intent.data != null) {
val packageName = intent.data!!.encodedSchemeSpecificPart
when (intent.action) {
Intent.ACTION_PACKAGE_ADDED -> {
EventBus.getDefault()
.post(InstallEvent(packageName, ""))
//Clear installation queue
AppInstaller.with(context)
.getPreferredInstaller()
.removeFromInstallQueue(packageName)
}
Intent.ACTION_PACKAGE_REMOVED -> EventBus.getDefault()
.post(UninstallEvent(packageName, ""))
}
clearNotification(context, packageName)
val isAutoDeleteAPKEnabled = Preferences.getBoolean(
context,
Preferences.PREFERENCE_AUTO_DELETE
)
if (isAutoDeleteAPKEnabled)
clearDownloads(context, packageName)
}
}
private fun clearNotification(context: Context, packageName: String) {
val notificationManager = context.applicationContext
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(packageName, packageName.hashCode())
}
private fun clearDownloads(context: Context, packageName: String) {
try {
val rootDirPath = PathUtil.getPackageDirectory(context, packageName)
val rootDir = File(rootDirPath)
if (rootDir.exists())
rootDir.deleteRecursively()
} catch (e: Exception) {
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class UpdatesReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
}
}

View File

@@ -0,0 +1,378 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.data.service
import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.IBinder
import android.util.ArrayMap
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.store.R
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.receiver.DownloadCancelReceiver
import com.aurora.store.data.receiver.DownloadPauseReceiver
import com.aurora.store.data.receiver.DownloadResumeReceiver
import com.aurora.store.data.receiver.InstallReceiver
import com.aurora.store.util.CommonUtil
import com.aurora.store.util.Log
import com.aurora.store.util.extensions.isLAndAbove
import com.aurora.store.view.ui.details.AppDetailsActivity
import com.aurora.store.view.ui.downloads.DownloadActivity
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.tonyodev.fetch2.*
import org.apache.commons.lang3.StringUtils
import java.lang.reflect.Modifier
import java.util.*
class NotificationService : Service() {
companion object {
fun startService(context: Context) {
try {
context.startService(Intent(context, NotificationService::class.java))
} catch (e: Exception) {
Log.e("Failed to start notification service : %s", e.message)
}
}
}
private lateinit var fetch: Fetch
private lateinit var fetchListener: AbstractFetchGroupListener
private lateinit var notificationManager: NotificationManager
private val appMap = ArrayMap<Int, App>()
private val gson: Gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT)
.create()
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return START_NOT_STICKY
}
override fun onCreate() {
super.onCreate()
Log.i("Notification Service Started")
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
//Create Notification Channels : General & Alert
createNotificationChannel()
fetch = DownloadManager.with(this).fetch
fetchListener = object : AbstractFetchGroupListener() {
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
showNotification(groupId, download, fetchGroup)
}
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
showNotification(groupId, download, fetchGroup)
if (fetchGroup.groupDownloadProgress == 100) {
install(download.tag!!, fetchGroup.downloads)
}
}
override fun onError(
groupId: Int,
download: Download,
error: Error,
throwable: Throwable?,
fetchGroup: FetchGroup
) {
showNotification(groupId, download, fetchGroup)
}
override fun onProgress(
groupId: Int,
download: Download,
etaInMilliSeconds: Long,
downloadedBytesPerSecond: Long,
fetchGroup: FetchGroup
) {
showNotification(groupId, download, fetchGroup)
}
override fun onQueued(
groupId: Int,
download: Download,
waitingNetwork: Boolean,
fetchGroup: FetchGroup
) {
showNotification(groupId, download, fetchGroup)
}
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
showNotification(groupId, download, fetchGroup)
}
}
fetch.addListener(fetchListener)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channels = ArrayList<NotificationChannel>()
channels.add(
NotificationChannel(
Constants.NOTIFICATION_CHANNEL_ALERT,
getString(R.string.notification_channel_alert),
NotificationManager.IMPORTANCE_HIGH
)
)
channels.add(
NotificationChannel(
Constants.NOTIFICATION_CHANNEL_GENERAL,
getString(R.string.notification_channel_general),
NotificationManager.IMPORTANCE_MIN
)
)
notificationManager.createNotificationChannels(channels)
}
}
private fun showNotification(groupId: Int, download: Download, fetchGroup: FetchGroup) {
val status = download.status
//Ignore notifications for completion of sub-parts of a bundled apk
if (status == Status.COMPLETED && fetchGroup.groupDownloadProgress < 100)
return
//synchronized(appMap) {
var app: App? = appMap[groupId]
if (app == null) {
app = gson.fromJson(
download.extras.getString(Constants.STRING_EXTRA, "{}"),
App::class.java
)
appMap[groupId] = app
}
if (app == null)
return
val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL)
builder.setContentTitle(app.displayName)
builder.setSmallIcon(R.drawable.ic_notification_outlined)
builder.color = ContextCompat.getColor(this, R.color.colorAccent)
builder.setWhen(download.created)
builder.setContentIntent(getContentIntentForDownloads())
when (status) {
Status.PAUSED -> {
builder.setSmallIcon(R.drawable.ic_download_pause)
builder.setContentText(getString(R.string.download_paused))
}
Status.CANCELLED -> {
builder.setSmallIcon(R.drawable.ic_download_cancel)
builder.setContentText(getString(R.string.download_canceled))
builder.color = Color.RED
}
Status.FAILED -> {
builder.setSmallIcon(R.drawable.ic_download_fail)
builder.setContentText(getString(R.string.download_failed))
builder.color = Color.RED
}
Status.COMPLETED -> if (fetchGroup.groupDownloadProgress == 100) {
builder.setSmallIcon(android.R.drawable.stat_sys_download_done)
builder.setContentText(getString(R.string.download_completed))
}
else -> {
builder.setSmallIcon(android.R.drawable.stat_sys_download)
builder.setContentText(getString(R.string.download_metadata))
}
}
val progress = fetchGroup.groupDownloadProgress
val progressBigText = NotificationCompat.BigTextStyle()
when (status) {
Status.QUEUED -> {
builder.setProgress(100, 0, true)
progressBigText.bigText(getString(R.string.download_queued))
builder.setStyle(progressBigText)
}
Status.DOWNLOADING -> {
val contentString = getString(R.string.download_progress)
val partString = StringUtils.joinWith(
"/",
fetchGroup.completedDownloads.size + 1,
fetchGroup.downloads.size
)
val speedString: String =
CommonUtil.humanReadableByteSpeed(download.downloadedBytesPerSecond, true)
progressBigText.bigText(
StringUtils.joinWith(
" \u2022 ",
contentString,
partString,
speedString
)
)
builder.setStyle(progressBigText)
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_download_pause,
getString(R.string.action_pause),
getPauseIntent(groupId)
).build()
)
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_download_cancel,
getString(R.string.action_cancel),
getCancelIntent(groupId)
).build()
)
if (progress < 0) builder.setProgress(
100,
0,
true
) else builder.setProgress(100, progress, false)
}
Status.PAUSED -> {
val pauseString = getString(R.string.download_paused)
val filesString = StringUtils.joinWith(
"/",
fetchGroup.completedDownloads.size,
fetchGroup.downloads.size
)
progressBigText.bigText(
StringUtils.joinWith(
" \u2022 ",
pauseString,
filesString
)
)
builder.setStyle(progressBigText)
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_download_pause,
getString(R.string.action_resume),
getResumeIntent(groupId)
).build()
)
}
Status.COMPLETED -> if (fetchGroup.groupDownloadProgress == 100) {
builder.setAutoCancel(true)
builder.setContentIntent(getContentIntentForDetails(app))
builder.setStyle(progressBigText)
}
else -> {
}
}
if (isLAndAbove()) {
when (status) {
Status.DOWNLOADING -> builder.setCategory(Notification.CATEGORY_PROGRESS)
Status.FAILED, Status.CANCELLED -> builder.setCategory(Notification.CATEGORY_ERROR)
else -> builder.setCategory(Notification.CATEGORY_STATUS)
}
}
notificationManager.notify(
app.packageName,
app.id,
builder.build()
)
//}
}
private fun getPauseIntent(groupId: Int): PendingIntent {
val intent = Intent(this, DownloadPauseReceiver::class.java)
intent.putExtra(Constants.FETCH_GROUP_ID, groupId)
return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun getResumeIntent(groupId: Int): PendingIntent {
val intent = Intent(this, DownloadResumeReceiver::class.java)
intent.putExtra(Constants.FETCH_GROUP_ID, groupId)
return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun getCancelIntent(groupId: Int): PendingIntent {
val intent = Intent(this, DownloadCancelReceiver::class.java)
intent.putExtra(Constants.FETCH_GROUP_ID, groupId)
return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun getContentIntentForDetails(app: App?): PendingIntent {
val intent = Intent(this, AppDetailsActivity::class.java)
intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app))
return PendingIntent.getActivity(
this,
packageName.hashCode(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
private fun getContentIntentForDownloads(): PendingIntent {
val intent = Intent(this, DownloadActivity::class.java)
return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun getInstallIntent(packageName: String, versionCode: String): PendingIntent {
val intent = Intent(this, InstallReceiver::class.java)
intent.putExtra(Constants.STRING_EXTRA, packageName)
return PendingIntent.getBroadcast(
this,
packageName.hashCode(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
@Synchronized
private fun install(packageName: String, files: List<Download>) {
AppInstaller.with(this)
.getPreferredInstaller()
.install(
packageName,
files
.filter { it.file.endsWith(".apk") }
.map {
it.file
}.toList()
)
}
override fun onDestroy() {
Log.i("Notification Service Stopped")
fetch.removeListener(fetchListener)
super.onDestroy()
}
}

View File

@@ -0,0 +1,66 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import com.github.kittinunf.fuel.Fuel
import java.util.*
class AC2DMTask {
@Throws(Exception::class)
fun getAC2DMResponse(email: String?, oAuthToken: String?): Map<String, String> {
if (email == null || oAuthToken == null)
return mapOf()
val params: MutableMap<String, Any> = hashMapOf()
params["lang"] = Locale.getDefault().toString().replace("_", "-")
params["google_play_services_version"] = PLAY_SERVICES_VERSION_CODE
params["sdk_version"] = BUILD_VERSION_SDK
params["device_country"] = Locale.getDefault().country.toLowerCase(Locale.US)
params["Email"] = email
params["service"] = "ac2dm"
params["get_accountid"] = 1
params["ACCESS_TOKEN"] = 1
params["callerPkg"] = "com.google.android.gms"
params["add_account"] = 1
params["Token"] = oAuthToken
params["callerSig"] = "38918a453d07199354f8b19af05ec6562ced5788"
val body = params.map { "${it.key}=${it.value}" }.joinToString(separator = "&")
val response = Fuel.post(TOKEN_AUTH_URL)
.body(body)
.header("app" to "com.google.android.gms")
.header("User-Agent" to "")
.header("Content-Type" to "application/x-www-form-urlencoded")
.response()
return response.third.fold(success = {
Util.parseResponse(String(it))
}, failure = {
mapOf()
})
}
companion object {
private const val TOKEN_AUTH_URL = "https://android.clients.google.com/auth"
private const val BUILD_VERSION_SDK = 28
private const val PLAY_SERVICES_VERSION_CODE = 19629032
}
}

View File

@@ -0,0 +1,122 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import com.aurora.store.BuildConfig
import com.aurora.store.util.extensions.isLAndAbove
import org.apache.commons.io.IOUtils
import java.io.File
import java.io.FileOutputStream
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class ApkCopier(private val context: Context, private val packageName: String) {
fun copy() {
val destination = File(PathUtil.getBaseCopyDirectory())
destination.let {
if (it.exists()) {
Log.i("Base copy directory is available")
} else {
it.mkdirs()
Log.e("Base copy directory is created : ${it.path}")
}
}
val packageInfo: PackageInfo = context.packageManager.getPackageInfo(
packageName,
PackageManager.GET_META_DATA
)
val baseApk = getBaseApk(packageInfo)
val fileList: MutableList<File?> = mutableListOf()
/*Add base APK*/
fileList.add(baseApk)
if (isLAndAbove()) {
val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs
if (splitSourceDirs != null && splitSourceDirs.isNotEmpty()) {
/*Add Split APKs*/
fileList.addAll(getSplitAPKs(packageInfo))
}
bundleAllAPKs(fileList)
} else {
bundleAllAPKs(fileList)
}
}
private fun getBaseApk(packageInfo: PackageInfo?): File? {
return if (packageInfo?.applicationInfo != null) {
File(packageInfo.applicationInfo.sourceDir)
} else {
null
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun getSplitAPKs(packageInfo: PackageInfo): MutableList<File> {
val fileList: MutableList<File> = ArrayList()
val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs
if (splitSourceDirs != null) {
for (fileName in splitSourceDirs) fileList.add(File(fileName))
}
return fileList
}
private fun bundleAllAPKs(fileList: List<File?>) {
try {
val fileOutputStream =
FileOutputStream(PathUtil.getBaseCopyDirectory() + "$packageName.zip")
val zipOutputStream = ZipOutputStream(fileOutputStream)
for (file in fileList) {
file?.let {
val zipEntry = ZipEntry(file.name)
zipOutputStream.putNextEntry(zipEntry)
IOUtils.copy(it.inputStream(), zipOutputStream)
zipOutputStream.closeEntry()
}
}
zipOutputStream.close()
} catch (e: Exception) {
e.printStackTrace()
Log.e("ApkCopier : %s", e.message)
}
}
fun getUri(file: File): Uri {
return FileProvider.getUriForFile(
context,
"${BuildConfig.APPLICATION_ID}.fileProvider",
file
)
}
}

View File

@@ -0,0 +1,100 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.content.Context
import android.content.pm.PackageManager
import com.aurora.store.util.extensions.isPAndAbove
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.*
object CertUtil {
private const val FDROID = "FDROID"
private const val GUARDIAN = "GUARDIANPROJECT.INFO"
private fun getX509Certificates(
context: Context,
packageName: String
): List<X509Certificate?> {
val certificates: MutableList<X509Certificate?> = mutableListOf()
val packageManager = context.applicationContext.packageManager
try {
val packageInfo = if (isPAndAbove())
packageManager.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES
)
else
packageManager.getPackageInfo(
packageName,
PackageManager.GET_SIGNATURES
)
val certificateFactory = CertificateFactory.getInstance("X509")
if (isPAndAbove()) {
packageInfo.signingInfo.apkContentsSigners.forEach {
val bytes = it.toByteArray()
val inputStream: InputStream = ByteArrayInputStream(bytes)
certificates.add(
certificateFactory!!.generateCertificate(inputStream) as X509Certificate
)
}
} else {
for (i in 0..packageInfo.signatures.size) {
val bytes = packageInfo.signatures[i].toByteArray()
val inStream: InputStream = ByteArrayInputStream(bytes)
certificates.add(
certificateFactory!!.generateCertificate(inStream) as X509Certificate
)
}
}
} catch (e: Exception) {
Log.e(e.message)
}
return certificates
}
fun isFDroidApp(context: Context, packageName: String): Boolean {
val certificates = getX509Certificates(context, packageName)
return if (certificates.isEmpty())
false
else {
val cert = certificates[0]
if (cert != null) {
if (cert.subjectDN != null) {
val DN = cert.subjectDN.name.toUpperCase(Locale.getDefault())
DN.contains(FDROID) || DN.contains(GUARDIAN)
} else {
false
}
} else {
false
}
}
}
}

View File

@@ -0,0 +1,189 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.core.app.ActivityOptionsCompat
import com.aurora.store.R
import java.text.DecimalFormat
import java.util.*
import kotlin.math.ln
import kotlin.math.pow
object CommonUtil {
private val siPrefixes: Map<Int, String> = hashMapOf(
Pair(1, ""),
Pair(3, " KB"),
Pair(6, " MB"),
Pair(9, " GB")
)
private val diPrefixes: Map<Int, String> = hashMapOf(
Pair(1, ""),
Pair(3, " K"),
Pair(6, " Million"),
Pair(9, " Billion")
)
fun addSiPrefix(value: Long): String {
if (value <= 0L)
return "NA"
var tempValue = value
var order = 0
while (tempValue >= 1000.0) {
tempValue /= 1000.toLong()
order += 3
}
return tempValue.toString() + siPrefixes[order]
}
fun addDiPrefix(value: Long): String {
if (value <= 0L)
return "NA"
var tempValue = value
var order = 0
while (tempValue >= 1000.0) {
tempValue /= 1000.0.toLong()
order += 3
}
return tempValue.toString() + diPrefixes[order]
}
fun getETAString(context: Context, etaInMilliSeconds: Long): String {
if (etaInMilliSeconds < 0) {
return context.getString(R.string.download_eta_calculating)
}
var seconds = (etaInMilliSeconds / 1000).toInt()
val hours = (seconds / 3600).toLong()
seconds -= (hours * 3600).toInt()
val minutes = (seconds / 60).toLong()
seconds -= (minutes * 60).toInt()
return when {
hours > 0 -> {
context.getString(R.string.download_eta_hrs, hours, minutes, seconds)
}
minutes > 0 -> {
context.getString(R.string.download_eta_min, minutes, seconds)
}
else -> {
context.getString(R.string.download_eta_sec, seconds)
}
}
}
fun humanReadableByteSpeed(bytes: Long, si: Boolean): String {
val unit = if (si) 1000 else 1024
if (bytes < unit) return "$bytes B"
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1].toString() + if (si) "" else "i"
return String.format(
Locale.getDefault(), "%.1f %sB/s",
bytes / unit.toDouble().pow(exp.toDouble()),
pre
)
}
fun humanReadableByteValue(bytes: Long, si: Boolean): String {
val unit = if (si) 1000 else 1024
if (bytes < unit) return "$bytes B"
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1].toString() + if (si) "" else "i"
return String.format(
Locale.getDefault(), "%.1f %sB",
bytes / unit.toDouble().pow(exp.toDouble()),
pre
)
}
fun getDownloadSpeedString(context: Context, downloadedBytesPerSecond: Long): String {
if (downloadedBytesPerSecond < 0) {
return context.getString(R.string.download_speed_estimating)
}
val kb = downloadedBytesPerSecond.toDouble() / 1000.toDouble()
val mb = kb / 1000.toDouble()
val decimalFormat = DecimalFormat(".##")
return when {
mb >= 1 -> {
context.getString(R.string.download_speed_mb, decimalFormat.format(mb))
}
kb >= 1 -> {
context.getString(R.string.download_speed_kb, decimalFormat.format(kb))
}
else -> {
context.getString(R.string.download_speed_bytes, downloadedBytesPerSecond)
}
}
}
fun getEmptyActivityBundle(context: Context): Bundle? {
return ActivityOptionsCompat.makeCustomAnimation(
context,
android.R.anim.fade_in,
android.R.anim.fade_out
).toBundle()
}
fun cleanupInstallationSessions(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val packageInstaller = context.packageManager.packageInstaller
for (sessionInfo in packageInstaller.mySessions) {
try {
val sessionId = sessionInfo.sessionId
packageInstaller.abandonSession(sessionInfo.sessionId)
Log.i("Abandoned session id -> %d", sessionId)
} catch (e: Exception) {
}
}
}
}
fun getThemeStyleById(themeId: Int): Int {
return when (themeId) {
0 -> R.style.AppTheme
1 -> R.style.AppTheme_Light
2 -> R.style.AppTheme_Dark
3 -> R.style.AppTheme_Black
4 -> R.style.AppTheme_DarkX
5 -> R.style.AppTheme_Darkord
else -> R.style.AppTheme
}
}
fun getAccentStyleById(accentId: Int): Int {
return when (accentId) {
1 -> R.style.Accent01
2 -> R.style.Accent02
3 -> R.style.Accent03
4 -> R.style.Accent04
5 -> R.style.Accent05
6 -> R.style.Accent06
7 -> R.style.Accent07
8 -> R.style.Accent08
9 -> R.style.Accent09
10 -> R.style.Accent10
11 -> R.style.Accent11
12 -> R.style.Accent12
else -> R.style.Accent01
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.content.Context
import android.util.Log
import java.io.File
import java.io.FileWriter
import java.io.IOException
object Log {
const val TAG = "¯\\_(ツ)_/¯ "
fun e(message: String?, vararg args: Any?) {
e(String.format(message!!, *args))
}
fun e(message: String?) {
Log.e(TAG, message!!)
}
fun i(message: String?, vararg args: Any?) {
i(String.format(message!!, *args))
}
fun i(message: String?) {
Log.i(TAG, message!!)
}
fun d(message: String?, vararg args: Any?) {
d(String.format(message!!, *args))
}
fun d(message: String?) {
Log.d(TAG, message!!)
}
fun w(message: String?, vararg args: Any?) {
w(String.format(message!!, *args))
}
fun w(message: String?) {
Log.w(TAG, message!!)
}
fun writeToFile(context: Context, obj: Any) {
try {
val out = FileWriter(File(context.filesDir, "AuroraLogs.txt"))
out.write(obj.toString())
out.close()
} catch (e: IOException) {
e(e.message)
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.app.ActivityOptions
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.store.data.model.Report
import com.aurora.store.view.ui.details.AppDetailsActivity
import com.aurora.store.view.ui.details.DetailsExodusActivity
import com.aurora.store.view.ui.details.DevAppsActivity
import com.aurora.store.view.ui.search.SearchResultsActivity
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.lang.reflect.Modifier
object NavigationUtil {
val gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create()
fun openDetailsActivity(context: Context, app: App) {
val intent = Intent(
context,
AppDetailsActivity::class.java
).apply {
putExtra(Constants.STRING_EXTRA, gson.toJson(app))
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity)
context.startActivity(intent, options.toBundle())
} else {
context.startActivity(intent)
}
}
fun openDevAppsActivity(context: Context, app: App) {
val intent = Intent(
context,
DevAppsActivity::class.java
).apply {
putExtra(Constants.STRING_APP, gson.toJson(app))
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity)
context.startActivity(intent, options.toBundle())
} else {
context.startActivity(intent)
}
}
fun openExodusActivity(context: Context, app: App, report: Report) {
val intent = Intent(
context,
DetailsExodusActivity::class.java
).apply {
putExtra(Constants.STRING_APP, gson.toJson(app))
putExtra(Constants.STRING_EXTRA, gson.toJson(report))
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity)
context.startActivity(intent, options.toBundle())
} else {
context.startActivity(intent)
}
}
fun openSearchActivity(context: Context) {
val intent = Intent(
context,
SearchResultsActivity::class.java
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity)
context.startActivity(intent, options.toBundle())
} else {
context.startActivity(intent)
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
object PackageUtil {
fun isInstalled(context: Context, packageName: String): Boolean {
return try {
context.packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
fun isUpdatable(context: Context, packageName: String, versionCode: Long): Boolean {
return try {
val packageInfo = getPackageInfo(context, packageName)
if (packageInfo != null) {
return versionCode > packageInfo.versionCode
}
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
fun isTv(context: Context): Boolean {
val uiMode = context.resources.configuration.uiMode
return uiMode and Configuration.UI_MODE_TYPE_MASK == Configuration.UI_MODE_TYPE_TELEVISION
}
fun getLaunchIntent(context: Context, packageName: String?): Intent? {
val isTv = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTv(context)
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (isTv) {
context.packageManager.getLeanbackLaunchIntentForPackage(packageName!!)
} else {
context.packageManager.getLaunchIntentForPackage(packageName!!)
}
} else {
context.packageManager.getLaunchIntentForPackage(packageName!!)
}
return if (intent == null) {
null
} else {
intent.addCategory(if (isTv) Intent.CATEGORY_LEANBACK_LAUNCHER else Intent.CATEGORY_LAUNCHER)
intent
}
}
@Throws(Exception::class)
fun getPackageInfo(context: Context, packageName: String?): PackageInfo? {
return context.packageManager.getPackageInfo(packageName!!, 0)
}
fun getAllPackages(context: Context): List<PackageInfo> {
val packageInfoSet: MutableList<PackageInfo> = mutableListOf()
val packageManager: PackageManager = context.packageManager
val flags: Int = getAllFlags()
val packageInfoList: List<PackageInfo> = packageManager.getInstalledPackages(flags)
for (packageInfo in packageInfoList) {
if (packageInfo.packageName != null && packageInfo.applicationInfo != null) {
packageInfoSet.add(packageInfo)
}
}
return packageInfoSet
}
fun getPackageInfoMap(context: Context): MutableMap<String, PackageInfo> {
val packageInfoSet: MutableMap<String, PackageInfo> = mutableMapOf()
val packageManager: PackageManager = context.packageManager
val flags: Int = PackageManager.GET_META_DATA
var packageInfoList: List<PackageInfo> = packageManager.getInstalledPackages(flags)
val isFdroidFilterEnabled = Preferences.getBoolean(
context,
Preferences.PREFERENCE_FILTER_FDROID
)
packageInfoList = packageInfoList
.filter {
it.packageName != null && it.applicationInfo != null && it.applicationInfo.enabled
}
if (isFdroidFilterEnabled) {
packageInfoList
.filter {
val packageInstaller = packageManager.getInstallerPackageName(it.packageName)
packageInstaller != "org.fdroid.fdroid.privileged"
}.filter {
!CertUtil.isFDroidApp(context, it.packageName)
}
}
packageInfoList.forEach {
packageInfoSet[it.packageName] = it
}
return packageInfoSet
}
fun getFilter(): IntentFilter {
val filter = IntentFilter()
filter.addDataScheme("package")
filter.addAction(Intent.ACTION_PACKAGE_INSTALL)
filter.addAction(Intent.ACTION_PACKAGE_ADDED)
filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
return filter
}
private fun getAllFlags(): Int {
var flags = (PackageManager.GET_META_DATA
or PackageManager.GET_ACTIVITIES
or PackageManager.GET_SERVICES
or PackageManager.GET_PROVIDERS
or PackageManager.GET_RECEIVERS)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
flags = flags or PackageManager.GET_DISABLED_COMPONENTS
flags = flags or PackageManager.GET_UNINSTALLED_PACKAGES
} else {
flags = flags or PackageManager.MATCH_DISABLED_COMPONENTS
flags = flags or PackageManager.MATCH_UNINSTALLED_PACKAGES
}
return flags
}
}

View File

@@ -0,0 +1,78 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.content.Context
import android.os.Environment
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.File
import com.aurora.store.util.extensions.isLAndAbove
fun Context.getInternalBaseDirectory(): String {
return filesDir.path
}
object PathUtil {
private fun getDownloadDirectory(context: Context): String {
return if (isLAndAbove())
context.getInternalBaseDirectory() + "/Downloads"
else
getExternalPath()
}
fun getPackageDirectory(context: Context, packageName: String): String {
return getDownloadDirectory(context) + "/$packageName"
}
private fun getVersionDirectory(
context: Context,
packageName: String,
versionCode: Int
): String {
return getPackageDirectory(context, packageName) + "/$versionCode"
}
fun getApkDownloadFile(context: Context, app: App, file: File): String {
return getVersionDirectory(context, app.packageName, app.versionCode) + "/${file.name}"
}
fun getApkDownloadFile(context: Context, packageName: String, versionCode: Int): String {
return getVersionDirectory(context, packageName, versionCode)
}
fun getExternalPath(): String {
return Environment.getExternalStorageDirectory().toString() + "/Aurora/"
}
fun getBaseCopyDirectory(): String {
return "${getExternalPath()}/files/export/"
}
private fun getObbDownloadPath(context: Context, app: App): String {
return Environment.getExternalStorageDirectory()
.toString() + "/Android/obb/" + app.packageName
}
fun getObbDownloadFile(context: Context, app: App, file: File): String {
val obbDir = getObbDownloadPath(context, app)
return "$obbDir/${file.name}"
}
}

View File

@@ -0,0 +1,114 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.content.Context
import android.content.SharedPreferences
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
object Preferences {
const val PREFERENCE_AUTH_DATA = "PREFERENCE_AUTH_DATA"
const val PREFERENCE_INSTALLER_ID = "PREFERENCE_INSTALLER_ID"
const val PREFERENCE_THEME_TYPE = "PREFERENCE_THEME_TYPE"
const val PREFERENCE_THEME_ACCENT = "PREFERENCE_THEME_ACCENT"
const val PREFERENCE_INTRO = "PREFERENCE_INTRO"
const val PREFERENCE_FILTER_GOOGLE = "PREFERENCE_FILTER_GOOGLE"
const val PREFERENCE_FILTER_FDROID = "PREFERENCE_FILTER_FDROID"
const val PREFERENCE_AUTO_INSTALL = "PREFERENCE_AUTO_INSTALL"
const val PREFERENCE_AUTO_DELETE = "PREFERENCE_AUTO_DELETE"
const val INSTALLATION_ABANDON_SESSION = "INSTALLATION_ABANDON_SESSION"
const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE"
const val PREFERENCE_DOWNLOAD_WIFI = "PREFERENCE_DOWNLOAD_WIFI"
private fun getPrefs(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
fun putString(context: Context, key: String, value: String) {
getPrefs(context).edit().putString(key, value).apply()
}
fun putInteger(context: Context, key: String, value: Int) {
getPrefs(context).edit().putInt(key, value).apply()
}
fun putFloat(context: Context, key: String, value: Float) {
getPrefs(context).edit().putFloat(key, value).apply()
}
fun putLong(context: Context, key: String, value: Long) {
getPrefs(context).edit().putLong(key, value).apply()
}
fun putBoolean(context: Context, key: String, value: Boolean) {
getPrefs(context).edit().putBoolean(key, value).apply()
}
fun getString(context: Context, key: String): String {
return getPrefs(context).getString(key, "").toString()
}
fun getInteger(context: Context, key: String): Int {
return getPrefs(context).getInt(key, 0)
}
fun getFloat(context: Context, key: String): Float {
return getPrefs(context).getFloat(key, 0.0f)
}
fun getLong(context: Context, key: String): Long {
return getPrefs(context).getLong(key, 0L)
}
fun getBoolean(context: Context, key: String): Boolean {
return getPrefs(context).getBoolean(key, false)
}
}
fun Context.save(key: String, value: Int) {
Preferences.putInteger(this, key, value)
}
fun Fragment.save(key: String, value: Int) {
Preferences.putInteger(requireContext(), key, value)
}
fun Context.save(key: String, value: Boolean) {
Preferences.putBoolean(this, key, value)
}
fun Fragment.save(key: String, value: Boolean) {
Preferences.putBoolean(requireContext(), key, value)
}
fun Context.save(key: String, value: String) {
Preferences.putString(this, key, value)
}
fun Fragment.save(key: String, value: String) {
Preferences.putString(requireContext(), key, value)
}

View File

@@ -0,0 +1,53 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Util {
public static Map<String, String> parseResponse(String response) {
Map<String, String> keyValueMap = new HashMap<String, String>();
StringTokenizer st = new StringTokenizer(response, "\n\r");
while (st.hasMoreTokens()) {
String[] keyValue = st.nextToken().split("=", 2);
if (keyValue.length >= 2) {
keyValueMap.put(keyValue[0], keyValue[1]);
}
}
return keyValueMap;
}
public static Map<String, String> parseCookieString(String cookies) {
Map<String, String> cookieList = new HashMap<>();
Pattern cookiePattern = Pattern.compile("([^=]+)=([^;]*);?\\s?");
Matcher matcher = cookiePattern.matcher(cookies);
while (matcher.find()) {
String cookieKey = matcher.group(1);
String cookieValue = matcher.group(2);
cookieList.put(cookieKey, cookieValue);
}
return cookieList;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import android.view.WindowManager
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityOptionsCompat
object ViewUtil {
fun getEmptyActivityBundle(context: Context?): Bundle? {
return ActivityOptionsCompat.makeCustomAnimation(
context!!,
android.R.anim.fade_in,
android.R.anim.fade_out
).toBundle()
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun configureActivityLayout(activity: Activity, isLight: Boolean) {
val window = activity.window
val params = window.attributes
params.flags = params.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
window.attributes = params
window.statusBarColor = Color.TRANSPARENT
setFullScreenLightStatusBar(activity, isLight)
}
private fun setFullScreenLightStatusBar(activity: Activity, isLight: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var flags = activity.window.decorView.systemUiVisibility
if (isLight)
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
activity.window.decorView.systemUiVisibility = flags
}
}
fun getStyledAttribute(context: Context, styleID: Int): Int {
val arr = context.obtainStyledAttributes(TypedValue().data, intArrayOf(styleID))
val styledColor = arr.getColor(0, Color.WHITE)
arr.recycle()
return styledColor
}
}

View File

@@ -0,0 +1,30 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
fun <T> MutableList<T>.flushAndAdd(list: List<T>) {
clear()
addAll(list)
}
fun <T> MutableSet<T>.flushAndAdd(list: Set<T>) {
clear()
addAll(list)
}

View File

@@ -0,0 +1,46 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
import android.os.Build
import android.text.format.DateFormat
import java.util.*
fun Long.toDate(): String {
val calendar = Calendar.getInstance(Locale.getDefault())
calendar.timeInMillis = this
return DateFormat.format("dd/MM/yy", calendar).toString()
}
fun isLAndAbove(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
}
fun isNAndAbove(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
}
fun isPAndAbove(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
}
fun isQAndAbove(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
}

View File

@@ -0,0 +1,114 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ShareCompat
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.store.MainActivity
import com.aurora.store.R
import com.aurora.store.util.Log
import com.aurora.store.util.ViewUtil
import kotlin.system.exitProcess
fun Context.browse(url: String) {
try {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(url)
)
)
} catch (e: Exception) {
Log.e(e.message)
}
}
fun Context.share(app: App) {
try {
ShareCompat.IntentBuilder.from(this as AppCompatActivity)
.setType("text/plain")
.setChooserTitle(getString(R.string.action_share))
.setSubject(app.displayName)
.setText(Constants.SHARE_URL + app.packageName)
.startChooser()
} catch (e: Exception) {
}
}
fun Context.openInfo(packageName: String) {
try {
val intent = Intent(
"android.settings.APPLICATION_DETAILS_SETTINGS",
Uri.parse("package:$packageName")
)
startActivity(intent)
} catch (e: Exception) {
}
}
fun <T> Context.open(className: Class<T>, newTask: Boolean = false) {
val intent = Intent(this, className)
if (newTask)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(
intent,
ViewUtil.getEmptyActivityBundle(this)
)
}
fun AppCompatActivity.close() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAfterTransition()
} else {
finish()
}
}
fun Context.restartApp() {
val pendingIntent = PendingIntent.getActivity(
this,
1337,
Intent(this, MainActivity::class.java),
PendingIntent.FLAG_CANCEL_CURRENT
)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent)
exitProcess(0)
}
fun Context.copyToClipBoard(data: String?) {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Download Url", data)
clipboard.setPrimaryClip(clip)
}

View File

@@ -0,0 +1,165 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.annotation.RawRes
import com.bumptech.glide.Glide
import com.bumptech.glide.TransitionOptions
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.ViewTarget
import com.bumptech.glide.request.transition.DrawableCrossFadeFactory
import java.io.File
fun ImageView.load(
bitmap: Bitmap?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions? = null
): ViewTarget<ImageView, Drawable> = loadAny(bitmap, transitionOptions, requestOptions)
@JvmSynthetic
fun ImageView.load(
byteArray: ByteArray?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions? = null
): ViewTarget<ImageView, Drawable> = loadAny(byteArray, transitionOptions, requestOptions)
@JvmSynthetic
fun ImageView.load(
drawable: Drawable?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions? = null
): ViewTarget<ImageView, Drawable> = loadAny(drawable, transitionOptions, requestOptions)
@JvmSynthetic
fun ImageView.load(
@RawRes @DrawableRes resourceId: Int?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions? = null
): ViewTarget<ImageView, Drawable> = loadAny(resourceId, transitionOptions, requestOptions)
@JvmSynthetic
fun ImageView.load(
uri: Uri?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions? = null
): ViewTarget<ImageView, Drawable> = loadAny(uri, transitionOptions, requestOptions)
@JvmSynthetic
fun ImageView.load(
string: String?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions? = null
): ViewTarget<ImageView, Drawable> = loadAny(string, transitionOptions, requestOptions)
@JvmSynthetic
fun ImageView.load(
file: File?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions? = null
): ViewTarget<ImageView, Drawable> = loadAny(file, transitionOptions, requestOptions)
@JvmSynthetic
inline fun ImageView.load(
bitmap: Bitmap?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions.() -> Unit
): ViewTarget<ImageView, Drawable> = loadAny(bitmap, transitionOptions, requestOptions)
@JvmSynthetic
inline fun ImageView.load(
byteArray: ByteArray?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions.() -> Unit
): ViewTarget<ImageView, Drawable> = loadAny(byteArray, transitionOptions, requestOptions)
@JvmSynthetic
inline fun ImageView.load(
drawable: Drawable?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions.() -> Unit
): ViewTarget<ImageView, Drawable> = loadAny(drawable, transitionOptions, requestOptions)
@JvmSynthetic
inline fun ImageView.load(
@RawRes @DrawableRes resourceId: Int?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions.() -> Unit
): ViewTarget<ImageView, Drawable> = loadAny(resourceId, transitionOptions, requestOptions)
@JvmSynthetic
inline fun ImageView.load(
uri: Uri?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions.() -> Unit
): ViewTarget<ImageView, Drawable> = loadAny(uri, transitionOptions, requestOptions)
@JvmSynthetic
inline fun ImageView.load(
string: String?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions.() -> Unit
): ViewTarget<ImageView, Drawable> = loadAny(string, transitionOptions, requestOptions)
@JvmSynthetic
inline fun ImageView.load(
file: File?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions.() -> Unit
): ViewTarget<ImageView, Drawable> = loadAny(file, transitionOptions, requestOptions)
@JvmSynthetic
fun ImageView.loadAny(
data: Any?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions? = null
): ViewTarget<ImageView, Drawable> {
return Glide.with(this)
.load(data)
.apply {
transitionOptions?.let { transition(it) }
requestOptions?.let { apply(it) }
}
.into(this)
}
@JvmSynthetic
inline fun ImageView.loadAny(
data: Any?,
transitionOptions: TransitionOptions<*, Drawable>? = null,
requestOptions: RequestOptions.() -> Unit
): ViewTarget<ImageView, Drawable> {
val factory = DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build()
return Glide.with(this)
.load(data)
.transition(withCrossFade(factory))
.apply(RequestOptions().apply(requestOptions))
.into(this)
}
@JvmSynthetic
fun ImageView.clear() {
//Glide.with(this).clear(this)
}

View File

@@ -0,0 +1,28 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
import android.content.res.Resources
val Number.dp: Number
get() = (this.toFloat() / Resources.getSystem().displayMetrics.density).toInt()
val Number.px: Number
get() = (this.toFloat() * Resources.getSystem().displayMetrics.density).toInt()

View File

@@ -0,0 +1,148 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
import android.os.Build
import android.view.View
import android.view.WindowInsetsController
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.graphics.ColorUtils
import androidx.fragment.app.Fragment
import com.aurora.Constants
import com.aurora.store.R
import com.aurora.store.util.CommonUtil
import com.aurora.store.util.ViewUtil
fun Fragment.applyTheme(
themeId: Int,
accentId: Int = 1,
shouldApplyTransition: Boolean = true,
position: Int = 2
) {
val themeStyle = CommonUtil.getThemeStyleById(themeId)
if (themeStyle == 0) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
(requireActivity() as AppCompatActivity).applyDayNightMask()
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
/*Apply Theme*/
requireContext().theme.applyStyle(themeStyle, true)
/*Apply transition only on AppCompatActivity*/
if (shouldApplyTransition)
(requireActivity() as AppCompatActivity).transitionRecreate(position)
else
(requireActivity() as AppCompatActivity).recreate()
if (themeId == 1) {
(requireActivity() as AppCompatActivity).setLightConfiguration()
}
}
fun AppCompatActivity.transitionRecreate(position: Int = 2) {
val intent = Intent(this, javaClass)
intent.putExtra(Constants.INT_EXTRA, position)
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
}
fun AppCompatActivity.applyTheme(themeId: Int, accentId: Int = 1) {
val themeStyle = CommonUtil.getThemeStyleById(themeId)
val accentStyle = CommonUtil.getAccentStyleById(accentId)
if (themeStyle == 0) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
applyDayNightMask()
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
/*Apply Theme*/
setTheme(themeStyle)
/*Apply Accent*/
theme.applyStyle(accentStyle, true)
/*Apply Light Configuration*/
if (themeId == 1) {
setLightConfiguration()
}
}
fun AppCompatActivity.applyDayNightMask() {
val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
if (currentNightMode == Configuration.UI_MODE_NIGHT_NO) {
setLightConfiguration()
}
}
fun AppCompatActivity.setLightConfiguration() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
setLightConfigurationO()
} else {
setLightConfigurationO()
}
}
@RequiresApi(Build.VERSION_CODES.R)
private fun AppCompatActivity.setLightConfigurationR() {
window?.insetsController?.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
or WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
}
private fun AppCompatActivity.setLightConfigurationO() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setLightStatusBar()
setLightNavigationBar()
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = ColorUtils.setAlphaComponent(Color.BLACK, 120)
}
}
private fun AppCompatActivity.setLightStatusBar() {
var flags = window.decorView.systemUiVisibility
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
window.decorView.systemUiVisibility = flags
}
private fun AppCompatActivity.setLightNavigationBar() {
var flags = window.decorView.systemUiVisibility
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window.navigationBarColor =
ViewUtil.getStyledAttribute(this, android.R.attr.colorBackground)
flags = flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
}
window.decorView.systemUiVisibility = flags
}

View File

@@ -0,0 +1,41 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
import android.os.Handler
import android.os.Looper
fun runAsync(action: () -> Unit) = Thread(Runnable(action)).start()
fun runOnUiThread(action: () -> Unit) {
if (isMainLooperAlive()) {
action()
} else {
Handler(Looper.getMainLooper()).post(Runnable(action))
}
}
fun runDelayed(delayMillis: Long, action: () -> Unit) =
Handler().postDelayed(Runnable(action), delayMillis)
fun runDelayedOnUiThread(delayMillis: Long, action: () -> Unit) =
Handler(Looper.getMainLooper()).postDelayed(Runnable(action), delayMillis)
private fun isMainLooperAlive() = Looper.myLooper() == Looper.getMainLooper()

View File

@@ -0,0 +1,36 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes
fun Context.toast(text: CharSequence): Toast =
Toast.makeText(this, text, Toast.LENGTH_SHORT).apply { show() }
fun Context.longToast(text: CharSequence): Toast =
Toast.makeText(this, text, Toast.LENGTH_LONG).apply { show() }
fun Context.toast(@StringRes resId: Int): Toast =
Toast.makeText(this, resId, Toast.LENGTH_SHORT).apply { show() }
fun Context.longToast(@StringRes resId: Int): Toast =
Toast.makeText(this, resId, Toast.LENGTH_LONG).apply { show() }

View File

@@ -0,0 +1,81 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.util.extensions
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
fun View.isVisible() = visibility == View.VISIBLE
fun View.isGone() = visibility == View.GONE
fun View.isInvisible() = visibility == View.INVISIBLE
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
fun View.showKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
this.requestFocus()
imm.showSoftInput(this, 0)
}
fun View.hideKeyboard(): Boolean {
try {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
return imm.hideSoftInputFromWindow(windowToken, 0)
} catch (ignored: RuntimeException) {
}
return false
}
fun View.setOnSingleClickListener(tolerance: Long = 500, onClick: (v: View) -> Unit) {
var lastClicked = 0L
val currentTimeMillis = System.currentTimeMillis()
setOnClickListener {
if (currentTimeMillis - lastClicked > tolerance) {
onClick(it)
lastClicked = currentTimeMillis
}
}
}
fun View.rotate(resetToZero: Boolean = true, duration: Long = 400) {
if (resetToZero)
rotation = 0f
animate().rotation(360f).setDuration(duration).start()
}
fun View.flip(resetToZero: Boolean = true, duration: Long = 400) {
if (resetToZero)
rotation = 0f
animate().rotation(180f).setDuration(duration).start()
}
fun View.getString(resourceId: Int): String {
return context.getString(resourceId)
}

View File

@@ -0,0 +1,86 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.custom
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout
import androidx.annotation.RequiresApi
import com.aurora.store.R
import com.aurora.store.databinding.ViewActionButtonBinding
class ActionButton : RelativeLayout {
private lateinit var B: ViewActionButtonBinding
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
val view = inflate(context, R.layout.view_action_button, this)
B = ViewActionButtonBinding.bind(view)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionButton)
val btnTxt = typedArray.getString(R.styleable.ActionButton_btnActionText)
B.btn.text = btnTxt
typedArray.recycle()
}
fun setText(text: String) {
B.btn.text = text
}
fun updateProgress(isVisible: Boolean) {
if (isVisible)
B.progress.visibility = View.VISIBLE
else
B.progress.visibility = View.INVISIBLE
}
fun addOnClickListener(onClickListener: OnClickListener) {
B.btn.setOnClickListener(onClickListener)
}
}

View File

@@ -0,0 +1,94 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.custom;
import android.graphics.PointF;
import android.view.animation.Interpolator;
public class CubicBezierInterpolator implements Interpolator {
public static final CubicBezierInterpolator DEFAULT = new CubicBezierInterpolator(0.25, 0.1, 0.25, 1);
public static final CubicBezierInterpolator EASE_OUT = new CubicBezierInterpolator(0, 0, .58, 1);
public static final CubicBezierInterpolator EASE_OUT_QUINT = new CubicBezierInterpolator(.23, 1, .32, 1);
public static final CubicBezierInterpolator EASE_IN = new CubicBezierInterpolator(.42, 0, 1, 1);
public static final CubicBezierInterpolator EASE_BOTH = new CubicBezierInterpolator(.42, 0, .58, 1);
public static final CubicBezierInterpolator EASE_IN_OUT_QUAD = new CubicBezierInterpolator(0.455, 0.03, 0.515, 0.955);
protected PointF start;
protected PointF end;
protected PointF a = new PointF();
protected PointF b = new PointF();
protected PointF c = new PointF();
public CubicBezierInterpolator(PointF start, PointF end) throws IllegalArgumentException {
if (start.x < 0 || start.x > 1) {
throw new IllegalArgumentException("startX value must be in the range [0, 1]");
}
if (end.x < 0 || end.x > 1) {
throw new IllegalArgumentException("endX value must be in the range [0, 1]");
}
this.start = start;
this.end = end;
}
public CubicBezierInterpolator(float startX, float startY, float endX, float endY) {
this(new PointF(startX, startY), new PointF(endX, endY));
}
public CubicBezierInterpolator(double startX, double startY, double endX, double endY) {
this((float) startX, (float) startY, (float) endX, (float) endY);
}
@Override
public float getInterpolation(float time) {
return getBezierCoordinateY(getXForTime(time));
}
protected float getBezierCoordinateY(float time) {
c.y = 3 * start.y;
b.y = 3 * (end.y - start.y) - c.y;
a.y = 1 - c.y - b.y;
return time * (c.y + time * (b.y + time * a.y));
}
protected float getXForTime(float time) {
float x = time;
float z;
for (int i = 1; i < 14; i++) {
z = getBezierCoordinateX(x) - time;
if (Math.abs(z) < 1e-3) {
break;
}
x -= z / getXDerivate(x);
}
return x;
}
private float getXDerivate(float t) {
return c.x + t * (2 * b.x + 3 * a.x * t);
}
private float getBezierCoordinateX(float time) {
c.x = 3 * start.x;
b.x = 3 * (end.x - start.x) - c.x;
a.x = 1 - c.x - b.x;
return time * (c.x + time * (b.x + time * a.x));
}
}

View File

@@ -0,0 +1,71 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.custom
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.widget.RelativeLayout
import androidx.annotation.RequiresApi
import com.aurora.store.R
import com.aurora.store.databinding.ViewRatingBinding
class RatingView : RelativeLayout {
private lateinit var B: ViewRatingBinding
var number = 0
var max = 0
var rating = 0
constructor(context: Context, number: Int, max: Int, rating: Int) : super(context) {
this.number = number
this.max = max
this.rating = rating
init(context)
}
constructor(context: Context?) : super(context) {}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
}
private fun init(context: Context) {
val view = inflate(context, R.layout.view_rating, this)
B = ViewRatingBinding.bind(view)
B.avgNum.text = number.toString()
B.avgRating.max = max
B.avgRating.progress = rating
}
}

View File

@@ -0,0 +1,89 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.custom
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import com.aurora.store.R
import com.aurora.store.databinding.ViewStateButtonBinding
class StateButton : RelativeLayout {
private lateinit var B: ViewStateButtonBinding
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
val view = inflate(context, R.layout.view_state_button, this)
B = ViewStateButtonBinding.bind(view)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.StateButton)
val btnTxt = typedArray.getString(R.styleable.StateButton_btnStateText)
val btnIcon = typedArray.getResourceId(
R.styleable.StateButton_btnStateIcon,
R.drawable.ic_arrow_right
)
B.btn.text = btnTxt
B.btn.icon = ContextCompat.getDrawable(context, btnIcon)
typedArray.recycle()
}
fun updateProgress(isVisible: Boolean) {
if (isVisible)
B.progress.visibility = View.VISIBLE
else
B.progress.visibility = View.INVISIBLE
}
fun addOnClickListener(onClickListener: OnClickListener) {
B.btn.setOnClickListener(onClickListener)
}
}

View File

@@ -0,0 +1,69 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.custom.layouts
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout
import com.aurora.store.R
import com.aurora.store.databinding.ViewActionHeaderBinding
class ActionHeaderLayout : RelativeLayout {
private lateinit var B: ViewActionHeaderBinding
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
val view = inflate(context, R.layout.view_action_header, this)
B = ViewActionHeaderBinding.bind(view)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionHeaderLayout)
val textPrimary = typedArray.getString(R.styleable.ActionHeaderLayout_headerTitle)
textPrimary?.let {
B.txtTitle.text = it
}
}
fun setHeader(header: String?) {
B.txtTitle.text = header
}
fun addClickListener(onclickListener: OnClickListener?) {
B.imgAction.visibility = View.VISIBLE
B.imgAction.setOnClickListener(onclickListener)
}
}

View File

@@ -0,0 +1,83 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.custom.layouts
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.widget.RelativeLayout
import androidx.annotation.RequiresApi
import com.aurora.store.R
import com.aurora.store.databinding.ViewDevInfoBinding
class ViewDevInfo : RelativeLayout {
private lateinit var B: ViewDevInfoBinding
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
val view = inflate(context, R.layout.view_dev_info, this)
B = ViewDevInfoBinding.bind(view)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DevInfoLayout)
val icon = typedArray.getResourceId(
R.styleable.DevInfoLayout_imgIcon,
R.drawable.ic_map_marker
)
val textPrimary = typedArray.getString(R.styleable.DevInfoLayout_txtTitle)
val textSecondary = typedArray.getString(R.styleable.DevInfoLayout_txtSubtitle)
B.img.setImageResource(icon)
B.txtTitle.text = textPrimary
B.txtSubtitle.text = textSecondary
typedArray.recycle()
}
fun setTxtSubtitle(text: String?) {
B.txtSubtitle.text = text
invalidate()
}
}

View File

@@ -0,0 +1,60 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.custom.preference
import android.content.Context
import android.util.AttributeSet
import androidx.preference.ListPreference
class AuroraListPreference : ListPreference {
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
}
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
}
override fun getPersistedString(defaultReturnValue: String?): String? {
return getPersistedInt(-1).toString()
}
override fun persistString(value: String?): Boolean {
return persistInt(Integer.valueOf(value))
}
}

View File

@@ -0,0 +1,176 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.custom.recycler
import android.view.View
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
abstract class EndlessRecyclerOnScrollListener : RecyclerView.OnScrollListener {
private var enabled = true
private var previousTotal = 0
private var isLoading = true
private var visibleThreshold = RecyclerView.NO_POSITION
var firstVisibleItem: Int = 0
private set
var visibleItemCount: Int = 0
private set
var totalItemCount: Int = 0
private set
private var isOrientationHelperVertical: Boolean = false
private var orientationHelper: OrientationHelper? = null
var currentPage = 0
private set
lateinit var layoutManager: RecyclerView.LayoutManager
private set
constructor()
constructor(layoutManager: RecyclerView.LayoutManager) {
this.layoutManager = layoutManager
}
constructor(visibleThreshold: Int) {
this.visibleThreshold = visibleThreshold
}
constructor(layoutManager: RecyclerView.LayoutManager, visibleThreshold: Int) {
this.layoutManager = layoutManager
this.visibleThreshold = visibleThreshold
}
private fun findFirstVisibleItemPosition(recyclerView: RecyclerView): Int {
val child = findOneVisibleChild(0, layoutManager.childCount, false, true)
return if (child == null) RecyclerView.NO_POSITION else recyclerView.getChildAdapterPosition(
child
)
}
private fun findLastVisibleItemPosition(recyclerView: RecyclerView): Int {
val child = findOneVisibleChild(recyclerView.childCount - 1, -1, false, true)
return if (child == null) RecyclerView.NO_POSITION else recyclerView.getChildAdapterPosition(
child
)
}
private fun findOneVisibleChild(
fromIndex: Int,
toIndex: Int,
completelyVisible: Boolean,
acceptPartiallyVisible: Boolean
): View? {
if (layoutManager.canScrollVertically() != isOrientationHelperVertical || orientationHelper == null) {
isOrientationHelperVertical = layoutManager.canScrollVertically()
orientationHelper = if (isOrientationHelperVertical)
OrientationHelper.createVerticalHelper(layoutManager)
else
OrientationHelper.createHorizontalHelper(layoutManager)
}
val mOrientationHelper = this.orientationHelper ?: return null
val start = mOrientationHelper.startAfterPadding
val end = mOrientationHelper.endAfterPadding
val next = if (toIndex > fromIndex) 1 else -1
var partiallyVisible: View? = null
var i = fromIndex
while (i != toIndex) {
val child = layoutManager.getChildAt(i)
if (child != null) {
val childStart = mOrientationHelper.getDecoratedStart(child)
val childEnd = mOrientationHelper.getDecoratedEnd(child)
if (childStart < end && childEnd > start) {
if (completelyVisible) {
if (childStart >= start && childEnd <= end) {
return child
} else if (acceptPartiallyVisible && partiallyVisible == null) {
partiallyVisible = child
}
} else {
return child
}
}
}
i += next
}
return partiallyVisible
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (enabled) {
if (!::layoutManager.isInitialized) {
layoutManager = recyclerView.layoutManager
?: throw RuntimeException("A LayoutManager is required")
}
if (visibleThreshold == RecyclerView.NO_POSITION) {
visibleThreshold =
findLastVisibleItemPosition(recyclerView) - findFirstVisibleItemPosition(
recyclerView
)
}
visibleItemCount = recyclerView.childCount
totalItemCount = layoutManager.itemCount
firstVisibleItem = findFirstVisibleItemPosition(recyclerView)
if (isLoading) {
if (totalItemCount > previousTotal) {
isLoading = false
previousTotal = totalItemCount
}
}
if (!isLoading && totalItemCount - visibleItemCount <= firstVisibleItem + visibleThreshold) {
currentPage++
onLoadMore(currentPage)
isLoading = true
}
}
}
fun enable(): EndlessRecyclerOnScrollListener {
enabled = true
return this
}
fun disable(): EndlessRecyclerOnScrollListener {
enabled = false
return this
}
@JvmOverloads
fun resetPageCount(page: Int = 0) {
previousTotal = 0
isLoading = true
currentPage = page
onLoadMore(currentPage)
}
abstract fun onLoadMore(currentPage: Int)
}

View File

@@ -0,0 +1,29 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.controller
import com.aurora.gplayapi.data.models.StreamCluster
class CategoryCarouselController(callbacks: Callbacks) : GenericCarouselController(callbacks) {
override fun applyFilter(streamBundle: StreamCluster): Boolean {
return streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters
}
}

View File

@@ -0,0 +1,30 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.controller
import com.aurora.gplayapi.data.models.StreamCluster
class EarlyAccessCarouselController(callbacks: Callbacks) : GenericCarouselController(callbacks) {
override fun applyFilter(streamBundle: StreamCluster): Boolean {
return streamBundle.clusterTitle.isNotBlank() //Filter noisy cluster
&& streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters
}
}

View File

@@ -0,0 +1,50 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.controller
import com.airbnb.epoxy.TypedEpoxyController
import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle
import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster
import com.aurora.store.view.epoxy.groups.EditorChoiceModelGroup
import com.aurora.store.view.epoxy.views.EditorHeadViewModel_
class EditorChoiceController(private val callbacks: Callbacks) :
TypedEpoxyController<List<EditorChoiceBundle>>() {
interface Callbacks {
fun onClick(editorChoiceCluster: EditorChoiceCluster)
}
override fun buildModels(editorChoiceBundles: List<EditorChoiceBundle>) {
editorChoiceBundles.forEach { editorChoiceBundle ->
val idPrefix = editorChoiceBundle.id
add(
EditorHeadViewModel_()
.id("header_${idPrefix}")
.title(editorChoiceBundle.bundleTitle)
)
editorChoiceBundle.bundleChoiceClusters.forEach {
add(EditorChoiceModelGroup(it, callbacks))
}
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.gara.store.view.epoxy.controller
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.flexbox.FlexboxLayoutManager
class FlexLayoutManager : FlexboxLayoutManager {
constructor(context: Context?) : super(context)
constructor(context: Context?, flexDirection: Int) : super(context, flexDirection)
constructor(context: Context?, flexDirection: Int, flexWrap: Int) : super(
context,
flexDirection,
flexWrap
)
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
override fun generateLayoutParams(lp: ViewGroup.LayoutParams): RecyclerView.LayoutParams {
return LayoutParams(lp)
}
}

View File

@@ -0,0 +1,102 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.controller
import com.airbnb.epoxy.TypedEpoxyController
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.store.R
import com.aurora.store.view.epoxy.groups.CarouselModelGroup
import com.aurora.store.view.epoxy.groups.CarouselShimmerGroup
import com.aurora.store.view.epoxy.views.AppListViewModel_
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
open class GenericCarouselController(private val callbacks: Callbacks) :
TypedEpoxyController<StreamBundle?>() {
interface Callbacks {
fun onHeaderClicked(streamCluster: StreamCluster)
fun onClusterScrolled(streamCluster: StreamCluster)
fun onAppClick(app: App)
fun onAppLongClick(app: App)
}
open fun applyFilter(streamBundle: StreamCluster): Boolean {
return streamBundle.clusterTitle.isNotBlank() //Filter noisy cluster
&& streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters
&& streamBundle.clusterAppList.count() > 1 //Filter clusters with single apps (mostly promotions)
}
override fun buildModels(streamBundle: StreamBundle?) {
setFilterDuplicates(true)
if (streamBundle == null) {
for (i in 1..2) {
add(
CarouselShimmerGroup()
.id(i)
)
}
} else {
if (streamBundle.streamClusters.isEmpty()) {
add(
NoAppViewModel_()
.id("no_app")
.icon(R.drawable.ic_apps)
.message("No apps available")
)
} else {
if (streamBundle.streamClusters.size == 1) {
streamBundle
.streamClusters
.values
.filter { applyFilter(it) }
.forEach { streamCluster ->
streamCluster.clusterAppList.forEach {
add(
AppListViewModel_()
.id(it.id)
.app(it)
.click { _ -> callbacks.onAppClick(it) }
)
}
}
} else {
streamBundle
.streamClusters
.values
.filter { applyFilter(it) }
.forEach { streamCluster ->
add(CarouselModelGroup(streamCluster, callbacks))
}
}
if (streamBundle.hasNext())
add(
AppProgressViewModel_()
.id("progress")
)
}
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.groups
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.Carousel
import com.airbnb.epoxy.ModelView
@ModelView(saveViewState = true, autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
class CarouselHorizontal(context: Context?) : Carousel(context) {
override fun createLayoutManager(): LayoutManager {
return LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
}
override fun getSnapHelperFactory(): Nothing? = null
}

View File

@@ -0,0 +1,102 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.groups
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyModelGroup
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.store.R
import com.aurora.store.util.Log
import com.aurora.store.view.epoxy.controller.GenericCarouselController
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
import com.aurora.store.view.epoxy.views.AppViewModel_
import com.aurora.store.view.epoxy.views.HeaderViewModel_
class CarouselModelGroup(
streamCluster: StreamCluster,
callbacks: GenericCarouselController.Callbacks
) :
EpoxyModelGroup(
R.layout.model_carousel_group, buildModels(
streamCluster,
callbacks
)
) {
companion object {
private fun buildModels(
streamCluster: StreamCluster,
callbacks: GenericCarouselController.Callbacks
): List<EpoxyModel<*>> {
val models = ArrayList<EpoxyModel<*>>()
val clusterViewModels = mutableListOf<EpoxyModel<*>>()
val idPrefix = streamCluster.id
models.add(
HeaderViewModel_()
.id("${idPrefix}_header")
.title(streamCluster.clusterTitle)
.browseUrl(streamCluster.clusterBrowseUrl)
.click { _ ->
callbacks.onHeaderClicked(streamCluster)
}
)
for (app in streamCluster.clusterAppList) {
clusterViewModels.add(
AppViewModel_()
.id(app.id)
.app(app)
.click { _ ->
callbacks.onAppClick(app)
}
.longClick { _ ->
callbacks.onAppLongClick(app)
false
}
.onBind { _, _, position ->
val itemCount = clusterViewModels.count()
if (itemCount >= 2) {
if (position == clusterViewModels.count() - 2) {
callbacks.onClusterScrolled(streamCluster)
Log.i("Cluster %s Scrolled", streamCluster.clusterTitle)
}
}
}
)
}
if (streamCluster.hasNext()) {
clusterViewModels.add(
AppProgressViewModel_()
.id("${idPrefix}_progress")
)
}
models.add(
CarouselHorizontalModel_()
.id("${idPrefix}_cluster")
.models(clusterViewModels)
)
return models
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.groups
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyModelGroup
import com.aurora.store.R
import com.aurora.store.view.epoxy.views.shimmer.AppViewShimmerModel_
import com.aurora.store.view.epoxy.views.shimmer.HeaderViewShimmerModel_
import java.util.*
import kotlin.collections.ArrayList
class CarouselShimmerGroup() :
EpoxyModelGroup(
R.layout.model_carousel_group, buildModels()
) {
companion object {
private fun buildModels(): List<EpoxyModel<*>> {
val models = ArrayList<EpoxyModel<*>>()
val clusterViewModels = mutableListOf<EpoxyModel<*>>()
val idPrefix = UUID.randomUUID()
for (i in 1..8) {
clusterViewModels.add(
AppViewShimmerModel_()
.id(i)
)
}
models.add(
HeaderViewShimmerModel_()
.id("shimmer_header")
)
models.add(
CarouselHorizontalModel_()
.id("cluster_${idPrefix}")
.models(clusterViewModels)
)
return models
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.groups
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyModelGroup
import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster
import com.aurora.store.R
import com.aurora.store.view.epoxy.controller.EditorChoiceController
import com.aurora.store.view.epoxy.views.EditorImageViewModel_
import com.aurora.store.view.epoxy.views.HeaderViewModel_
class EditorChoiceModelGroup(
editorChoiceCluster: EditorChoiceCluster,
callbacks: EditorChoiceController.Callbacks
) :
EpoxyModelGroup(
R.layout.model_editorchoice_group, buildModels(
editorChoiceCluster,
callbacks
)
) {
companion object {
private fun buildModels(
editorChoiceCluster: EditorChoiceCluster,
callbacks: EditorChoiceController.Callbacks
): List<EpoxyModel<*>> {
val models = ArrayList<EpoxyModel<*>>()
val clusterViewModels = mutableListOf<EpoxyModel<*>>()
val idPrefix = editorChoiceCluster.id
models.add(
HeaderViewModel_()
.id("header_${idPrefix}")
.title(editorChoiceCluster.clusterTitle)
.browseUrl(editorChoiceCluster.clusterBrowseUrl)
.click { _ -> callbacks.onClick(editorChoiceCluster) }
)
editorChoiceCluster.clusterArtwork.forEach {
clusterViewModels.add(
EditorImageViewModel_()
.id("artwork_${idPrefix}")
.artwork(it)
)
}
models.add(
CarouselHorizontalModel_()
.id("cluster_${idPrefix}")
.models(clusterViewModels)
)
return models
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.views
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.util.AttributeSet
import android.widget.RelativeLayout
import androidx.core.view.isVisible
import com.airbnb.epoxy.CallbackProp
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.aurora.store.R
import com.aurora.store.data.model.Accent
import com.aurora.store.databinding.ViewAccentBinding
@ModelView(
autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT,
baseModelClass = BaseView::class
)
class AccentView : RelativeLayout {
private lateinit var B: ViewAccentBinding
constructor(context: Context?) : super(context) {
init(context, null)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
private fun init(context: Context?, attrs: AttributeSet?) {
val view = inflate(context, R.layout.view_accent, this)
B = ViewAccentBinding.bind(view)
}
@ModelProp
fun accent(accent: Accent) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
B.img.backgroundTintList = ColorStateList.valueOf(Color.parseColor(accent.accent))
} else {
B.img.setBackgroundColor(Color.parseColor(accent.accent))
}
}
@ModelProp
fun markChecked(isChecked: Boolean) {
B.tick.isVisible = isChecked
}
@CallbackProp
fun click(onClickListener: OnClickListener?) {
B.root.setOnClickListener(onClickListener)
}
}

Some files were not shown because too many files have changed in this diff Show More