Initial commit
|
@ -0,0 +1,7 @@
|
|||
root = true
|
||||
|
||||
[*.{kt,kts}]
|
||||
ktlint_standard_no-unused-imports = disabled
|
||||
ktlint_standard_no-wildcard-imports = disabled
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
|
@ -0,0 +1,10 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
|
@ -0,0 +1,134 @@
|
|||
import httplib2
|
||||
import os
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import (
|
||||
HttpError,
|
||||
)
|
||||
|
||||
from oauth2client.service_account import ServiceAccountCredentials
|
||||
|
||||
|
||||
dir = "listing/google/"
|
||||
|
||||
|
||||
def load_listing_full_description(folder):
|
||||
file_path = dir + folder + "/store_description.html"
|
||||
with open(file_path, 'r') as file:
|
||||
data = file.read()
|
||||
return {
|
||||
'listing_store_google_full_description': data,
|
||||
}
|
||||
|
||||
|
||||
def load_listing_texts(folder):
|
||||
file_path = dir + folder + "/store_text.xml"
|
||||
with open(file_path, 'r') as file:
|
||||
data = file.read()
|
||||
# Parse xml to dictionary
|
||||
soup = BeautifulSoup(data, features="xml")
|
||||
return {el["name"]: el.string for el in soup.find_all('string')}
|
||||
|
||||
|
||||
"""
|
||||
Load the listings from a folder to a human-accessible
|
||||
dictionary object.
|
||||
"""
|
||||
|
||||
|
||||
def load_listing(folder):
|
||||
a = load_listing_full_description(folder)
|
||||
b = load_listing_texts(folder)
|
||||
return a | b
|
||||
|
||||
|
||||
package_name = "com.artemchep.keyguard"
|
||||
|
||||
language_base = "base"
|
||||
language_mapping = {
|
||||
'uk-UA': 'uk',
|
||||
'ca-ES': 'ca',
|
||||
'vi-VN': 'vi',
|
||||
}
|
||||
|
||||
listing_base = load_listing(language_base)
|
||||
listings = {}
|
||||
|
||||
for folder in os.listdir(dir):
|
||||
listing = load_listing(folder)
|
||||
# We check if this listing is different from the base
|
||||
# listing. If so, we do not want to add it.
|
||||
if listing == listing_base:
|
||||
continue
|
||||
# Fix language tag.
|
||||
bcp_47_tag = folder.replace("-r", "-")
|
||||
google_play_tag = language_mapping.get(bcp_47_tag, bcp_47_tag)
|
||||
# Append.
|
||||
listings[google_play_tag] = listing
|
||||
|
||||
print("Loaded listings for languages:")
|
||||
for language in listings.keys():
|
||||
print("- " + language)
|
||||
print()
|
||||
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_name(
|
||||
"service-account-google.json",
|
||||
scopes="https://www.googleapis.com/auth/androidpublisher",
|
||||
)
|
||||
|
||||
# Create an httplib2.Http object to handle our HTTP requests and authorize
|
||||
# it with the Credentials.
|
||||
http = httplib2.Http()
|
||||
http = credentials.authorize(http)
|
||||
|
||||
service = build("androidpublisher", "v3", http=http)
|
||||
|
||||
print("Uploading:")
|
||||
edit = service.edits().insert(packageName=package_name).execute()
|
||||
edit_id = edit["id"]
|
||||
|
||||
for language, listing in listings.items():
|
||||
listing_request = {
|
||||
'language': language,
|
||||
'title': listing["listing_store_google_app_name"],
|
||||
'shortDescription': listing["listing_store_google_short_description"],
|
||||
'fullDescription': listing["listing_store_google_full_description"],
|
||||
}
|
||||
listing_response = None
|
||||
try:
|
||||
# See:
|
||||
# https://developers.google.com/android-publisher/api-ref/rest/v3/edits.listings/patch
|
||||
listing_response = service.edits()\
|
||||
.listings()\
|
||||
.patch(
|
||||
packageName=package_name,
|
||||
editId=edit_id,
|
||||
language=language,
|
||||
body=listing_request,
|
||||
).execute()
|
||||
except HttpError as e:
|
||||
if e.status_code == 404:
|
||||
# See:
|
||||
# https://developers.google.com/android-publisher/api-ref/rest/v3/edits.listings/update
|
||||
listing_response = service.edits()\
|
||||
.listings()\
|
||||
.update(
|
||||
packageName=package_name,
|
||||
editId=edit_id,
|
||||
language=language,
|
||||
body=listing_request,
|
||||
).execute()
|
||||
else:
|
||||
raise e
|
||||
print("- " + language + " done!")
|
||||
|
||||
|
||||
service.edits()\
|
||||
.commit(
|
||||
packageName=package_name,
|
||||
editId=edit_id,
|
||||
).execute()
|
||||
|
||||
print("Success!")
|
|
@ -0,0 +1,4 @@
|
|||
google-api-python-client
|
||||
oauth2client
|
||||
beautifulsoup4
|
||||
lxml
|
|
@ -0,0 +1,14 @@
|
|||
import json
|
||||
import requests
|
||||
|
||||
URL_APPS = "https://www.gstatic.com/gpm-passkeys-privileged-apps/apps.json"
|
||||
|
||||
response = requests.get(
|
||||
URL_APPS,
|
||||
)
|
||||
|
||||
json_obj = response.json()
|
||||
json_text = json.dumps(json_obj, indent=2)
|
||||
|
||||
with open('common/src/commonMain/resources/MR/files/gpm_passkeys_privileged_apps.json', 'w') as f:
|
||||
f.write(json_text)
|
|
@ -0,0 +1 @@
|
|||
requests
|
|
@ -0,0 +1,35 @@
|
|||
import json
|
||||
import requests
|
||||
|
||||
URL_JDM = "https://raw.githubusercontent.com/jdm-contrib/jdm/master/_data/sites.json"
|
||||
|
||||
response = requests.get(
|
||||
URL_JDM,
|
||||
)
|
||||
|
||||
aggr = []
|
||||
|
||||
for site in response.json():
|
||||
entry = {
|
||||
'name': site['name'],
|
||||
'domains': site.get('domains', []),
|
||||
}
|
||||
|
||||
def append_if_not_empty(src_key, dst_key):
|
||||
if src_key in site and site[src_key]:
|
||||
entry[dst_key] = site[src_key]
|
||||
|
||||
append_if_not_empty('url', 'url')
|
||||
append_if_not_empty('difficulty', 'difficulty')
|
||||
append_if_not_empty('notes', 'notes')
|
||||
# for the extra-asshole websites
|
||||
append_if_not_empty('email', 'email')
|
||||
append_if_not_empty('email_subject', 'email_subject')
|
||||
append_if_not_empty('email_body', 'email_body')
|
||||
|
||||
aggr.append(entry)
|
||||
|
||||
aggr_text = json.dumps(aggr, indent=2)
|
||||
|
||||
with open('common/src/commonMain/resources/MR/files/justdeleteme.json', 'w') as f:
|
||||
f.write(aggr_text)
|
|
@ -0,0 +1 @@
|
|||
requests
|
|
@ -0,0 +1,35 @@
|
|||
import json
|
||||
import requests
|
||||
|
||||
URL_JGMD = "https://raw.githubusercontent.com/daviddavo/jgmd/master/_data/sites.json"
|
||||
|
||||
response = requests.get(
|
||||
URL_JGMD,
|
||||
)
|
||||
|
||||
aggr = []
|
||||
|
||||
for site in response.json():
|
||||
entry = {
|
||||
'name': site['name'],
|
||||
'domains': site.get('domains', []),
|
||||
}
|
||||
|
||||
def append_if_not_empty(src_key, dst_key):
|
||||
if src_key in site and site[src_key]:
|
||||
entry[dst_key] = site[src_key]
|
||||
|
||||
append_if_not_empty('url', 'url')
|
||||
append_if_not_empty('difficulty', 'difficulty')
|
||||
append_if_not_empty('notes', 'notes')
|
||||
# for the extra-asshole websites
|
||||
append_if_not_empty('email', 'email')
|
||||
append_if_not_empty('email_subject', 'email_subject')
|
||||
append_if_not_empty('email_body', 'email_body')
|
||||
|
||||
aggr.append(entry)
|
||||
|
||||
aggr_text = json.dumps(aggr, indent=2)
|
||||
|
||||
with open('common/src/commonMain/resources/MR/files/justgetmydata.json', 'w') as f:
|
||||
f.write(aggr_text)
|
|
@ -0,0 +1 @@
|
|||
requests
|
|
@ -0,0 +1,56 @@
|
|||
import json
|
||||
import requests
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser("update_passkeys")
|
||||
parser.add_argument("api_key", help="An API Key to access the https://passkeys.directory/ backend.", type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
URL_PASSKEYS = "https://apecbgwekadegtkzpwyh.supabase.co/rest/v1/sites"
|
||||
URL_APIKEY = args.api_key
|
||||
|
||||
response = requests.get(
|
||||
URL_PASSKEYS,
|
||||
params={
|
||||
'select': '*',
|
||||
'or': '(passkey_signin.eq.true,passkey_mfa.eq.true)',
|
||||
},
|
||||
headers={
|
||||
'apikey': URL_APIKEY,
|
||||
},
|
||||
)
|
||||
|
||||
aggr = []
|
||||
|
||||
for site in response.json():
|
||||
features = []
|
||||
entry = {
|
||||
'name': site['name'],
|
||||
'domain': site['domain'],
|
||||
'features': features,
|
||||
}
|
||||
|
||||
def append_if_not_empty(src_key, dst_key):
|
||||
if src_key in site and site[src_key]:
|
||||
entry[dst_key] = site[src_key]
|
||||
|
||||
append_if_not_empty('documentation_link', 'documentation')
|
||||
append_if_not_empty('setup_link', 'setup')
|
||||
append_if_not_empty('notes', 'notes')
|
||||
|
||||
# convert features to a list
|
||||
if site['passkey_mfa']:
|
||||
features.append('mfa')
|
||||
if site['passkey_signin']:
|
||||
features.append('signin')
|
||||
|
||||
aggr.append(entry)
|
||||
|
||||
# Ensure the file is sorted for better
|
||||
# git diff logs.
|
||||
aggr.sort(key=lambda x: x['domain'])
|
||||
|
||||
aggr_text = json.dumps(aggr, indent=2)
|
||||
|
||||
with open('common/src/commonMain/resources/MR/files/passkeys.json', 'w') as f:
|
||||
f.write(aggr_text)
|
|
@ -0,0 +1 @@
|
|||
requests
|
|
@ -0,0 +1,49 @@
|
|||
import re
|
||||
import json
|
||||
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
from urllib.request import urlopen
|
||||
|
||||
URL_2FA = "https://github.com/2factorauth/twofactorauth/archive/refs/heads/master.zip"
|
||||
|
||||
# Load the zip file without saving it into a file.
|
||||
response = urlopen(URL_2FA)
|
||||
archive = ZipFile(BytesIO(response.read()))
|
||||
|
||||
aggr = []
|
||||
|
||||
# Load each of the json files and save into
|
||||
# a single json array.
|
||||
for file in archive.namelist():
|
||||
if not re.match(r".+/entries/.+\.json", file):
|
||||
continue
|
||||
json_text = archive.read(file).decode('utf-8')
|
||||
json_obj = json.loads(json_text)
|
||||
# Flatten the structure.
|
||||
json_obj_root_key = list(json_obj.keys())[0]
|
||||
json_obj = json_obj[json_obj_root_key]
|
||||
# Add name
|
||||
if not "name" in json_obj:
|
||||
json_obj["name"] = json_obj_root_key
|
||||
# Delete unused attributes.
|
||||
if not "tfa" in json_obj:
|
||||
continue
|
||||
if "img" in json_obj:
|
||||
del json_obj["img"]
|
||||
if "recovery" in json_obj:
|
||||
del json_obj["recovery"]
|
||||
if "keywords" in json_obj:
|
||||
del json_obj["keywords"]
|
||||
if "categories" in json_obj:
|
||||
del json_obj["categories"]
|
||||
if "contact" in json_obj:
|
||||
del json_obj["contact"]
|
||||
if "regions" in json_obj:
|
||||
del json_obj["regions"]
|
||||
aggr.append(json_obj)
|
||||
|
||||
aggr_text = json.dumps(aggr, indent=2)
|
||||
|
||||
with open('common/src/commonMain/resources/MR/files/tfa.json', 'w') as f:
|
||||
f.write(aggr_text)
|
|
@ -0,0 +1,25 @@
|
|||
name: "✔️ Check Gradle wrapper"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'gradlew*'
|
||||
- 'gradle/wrapper/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'gradlew*'
|
||||
- 'gradle/wrapper/**'
|
||||
|
||||
jobs:
|
||||
check-gradle-wrapper:
|
||||
name: Check Gradle wrapper
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Check Gradle wrapper"
|
||||
uses: gradle/wrapper-validation-action@v1
|
|
@ -0,0 +1,36 @@
|
|||
name: "✔️ Check Licenses"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**/*.gradle*'
|
||||
- 'gradle/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**/*.gradle*'
|
||||
- 'gradle/**'
|
||||
|
||||
jobs:
|
||||
check-licenses:
|
||||
name: Check Licenses
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Set up JDK 17"
|
||||
id: setup-java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
- name: "Check licenses"
|
||||
uses: eskatos/gradle-command-action@v2
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
with:
|
||||
arguments: licensee
|
|
@ -0,0 +1,33 @@
|
|||
name: "🤖 Daily tag"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
new-tag:
|
||||
name: New tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PERSONAL_TOKEN }}
|
||||
- name: "Create a new daily tag"
|
||||
run: |
|
||||
tag=d"$(date -u +'%Y%m%d')"
|
||||
echo "Name of a new tag is $tag"
|
||||
tag_commit=$(git rev-list -n 1 $(git describe --tags --abbrev=0))
|
||||
echo "$tag -> $tag_commit"
|
||||
master_commit=$(git rev-list -n 1 master)
|
||||
echo "master -> $master_commit"
|
||||
# Create a new tag only if the latest tag does not
|
||||
# point to the same commit as the master branch.
|
||||
if [ "$tag_commit" != "$master_commit" ]; then
|
||||
git config user.email github-actions@github.com
|
||||
git config user.name "${{ github.actor }}"
|
||||
git tag $tag
|
||||
git push origin $tag
|
||||
fi
|
|
@ -0,0 +1,53 @@
|
|||
name: "🤖 Daily tag -> Play Store internal track"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'd*'
|
||||
|
||||
jobs:
|
||||
build-play-store-internal:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Set up JDK 17"
|
||||
id: setup-java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
- id: vars
|
||||
run: |
|
||||
echo ::set-output name=tag::${GITHUB_REF:11}
|
||||
- name: "Prepare env"
|
||||
run: |
|
||||
echo ${{ secrets.KEYSTORE_B64 }} | base64 -d | zcat >> androidApp/keyguard-release.keystore
|
||||
echo ${{ secrets.KEYSTORE_PROPS_B64 }} | base64 -d | zcat >> androidApp/keyguard-release.properties
|
||||
echo ${{ secrets.GOOGLE_SERVICES }} | base64 -d | zcat >> androidApp/google-services.json
|
||||
echo ${{ secrets.SERVICE_ACCOUNT_B64 }} | base64 -d | zcat >> service-account-google.json
|
||||
echo "" >> gradle.properties
|
||||
echo versionDate=${{ steps.vars.outputs.tag }} >> gradle.properties
|
||||
echo buildkonfig.flavor=release >> gradle.properties
|
||||
- name: "Check and Build licenses"
|
||||
uses: eskatos/gradle-command-action@v2
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
with:
|
||||
arguments: :androidApp:licenseeAndroidPlayStoreRelease
|
||||
- name: "Move licenses"
|
||||
run: |
|
||||
mv -f androidApp/build/reports/licensee/androidPlayStoreRelease/artifacts.json common/src/commonMain/resources/MR/files/licenses.json
|
||||
- name: "Build release bundle"
|
||||
uses: eskatos/gradle-command-action@v2
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
with:
|
||||
arguments: :androidApp:bundlePlayStoreRelease
|
||||
- name: "Distribute on Play Store"
|
||||
uses: r0adkll/upload-google-play@v1.1.2
|
||||
with:
|
||||
serviceAccountJson: service-account-google.json
|
||||
packageName: com.artemchep.keyguard
|
||||
releaseFiles: androidApp/build/outputs/bundle/playStoreRelease/androidApp-playStore-release.aab
|
||||
track: internal
|
|
@ -0,0 +1,232 @@
|
|||
name: "🎉 Release tag -> GitHub release"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'r*'
|
||||
|
||||
jobs:
|
||||
build-macos-app:
|
||||
runs-on: macos-12
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
submodules: recursive
|
||||
- name: "Set up JDK 17"
|
||||
id: setup-java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
- name: "Decode signing certificate"
|
||||
run: |
|
||||
echo ${{ secrets.CERT_B64 }} | base64 -d | zcat > desktopApp/macos-dev.cer
|
||||
- name: "Import signing certificate"
|
||||
uses: apple-actions/import-codesign-certs@v2
|
||||
with:
|
||||
p12-filepath: desktopApp/macos-dev.cer
|
||||
p12-password: ${{ secrets.CERT_PASSWD }}
|
||||
- name: "Setup build env"
|
||||
run: |
|
||||
echo "" >> gradle.properties
|
||||
echo buildkonfig.flavor=release >> gradle.properties
|
||||
- name: "Setup signing config"
|
||||
run: |
|
||||
echo "" >> gradle.properties
|
||||
echo "cert_identity=${{ secrets.CERT_IDENTITY }}" >> gradle.properties
|
||||
- name: "Setup notarization config"
|
||||
run: |
|
||||
echo "" >> gradle.properties
|
||||
echo "notarization_apple_id=${{ secrets.NOTARIZATION_APPLE_ID }}" >> gradle.properties
|
||||
echo "notarization_password=${{ secrets.NOTARIZATION_PASSWD }}" >> gradle.properties
|
||||
echo "notarization_asc_provider=${{ secrets.NOTARIZATION_ASC_PROVIDER }}" >> gradle.properties
|
||||
- name: "./gradlew :desktopApp:packageDmg :desktopApp:notarizeDmg"
|
||||
uses: eskatos/gradle-command-action@v2
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
with:
|
||||
arguments: "-PexcludeAppImage=true :desktopApp:packageDmg :desktopApp:notarizeDmg"
|
||||
- name: 'Upload logs'
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: logs-mac
|
||||
path: desktopApp/build/compose/logs/**/*.txt
|
||||
retention-days: 30
|
||||
- name: 'Upload .dmg'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-mac
|
||||
path: desktopApp/build/compose/binaries/main/dmg/*.dmg
|
||||
retention-days: 1
|
||||
build-linux-flatpak-app:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: bilelmoussaoui/flatpak-github-actions:gnome-45
|
||||
options: --privileged
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
submodules: recursive
|
||||
- name: "Set up JDK 17"
|
||||
id: setup-java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
- name: "Setup build env"
|
||||
run: |
|
||||
echo "" >> gradle.properties
|
||||
echo buildkonfig.flavor=release >> gradle.properties
|
||||
- name: "./gradlew :desktopApp:bundleFlatpak"
|
||||
uses: eskatos/gradle-command-action@v2
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
with:
|
||||
arguments: ":desktopApp:bundleFlatpak"
|
||||
- name: 'Upload .flatpak'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-linux-flatpak
|
||||
path: desktopApp/build/flatpak/Keyguard.flatpak
|
||||
retention-days: 1
|
||||
build-windows-app:
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
submodules: recursive
|
||||
- name: "Set up JDK 17"
|
||||
id: setup-java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: |
|
||||
11
|
||||
17
|
||||
cache: 'gradle'
|
||||
- name: "Setup build env"
|
||||
run: |
|
||||
echo "" >> gradle.properties
|
||||
echo buildkonfig.flavor=release >> gradle.properties
|
||||
- name: "./gradlew :desktopApp:packageMsi"
|
||||
uses: eskatos/gradle-command-action@v2
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
with:
|
||||
arguments: ":desktopApp:packageMsi"
|
||||
- name: 'Upload .msi'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-windows
|
||||
path: desktopApp/build/compose/binaries/main/msi/*.msi
|
||||
retention-days: 1
|
||||
build-android-app:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Set up JDK 17"
|
||||
id: setup-java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
- id: vars
|
||||
run: |
|
||||
echo ::set-output name=tag::${GITHUB_REF:11}
|
||||
- name: "Prepare env"
|
||||
run: |
|
||||
echo ${{ secrets.KEYSTORE_B64 }} | base64 -d | zcat >> androidApp/keyguard-release.keystore
|
||||
echo ${{ secrets.KEYSTORE_PROPS_B64 }} | base64 -d | zcat >> androidApp/keyguard-release.properties
|
||||
echo ${{ secrets.GOOGLE_SERVICES }} | base64 -d | zcat >> androidApp/google-services.json
|
||||
echo "" >> gradle.properties
|
||||
echo versionDate=${{ steps.vars.outputs.tag }} >> gradle.properties
|
||||
echo buildkonfig.flavor=release >> gradle.properties
|
||||
- name: "Check and Build licenses"
|
||||
uses: eskatos/gradle-command-action@v2
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
with:
|
||||
arguments: :androidApp:licenseeAndroidNoneRelease
|
||||
- name: "Move licenses"
|
||||
run: |
|
||||
mv -f androidApp/build/reports/licensee/androidNoneRelease/artifacts.json common/src/commonMain/resources/MR/files/licenses.json
|
||||
- name: "./gradlew :androidApp:assembleNoneRelease"
|
||||
uses: eskatos/gradle-command-action@v2
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
with:
|
||||
arguments: :androidApp:assembleNoneRelease
|
||||
- name: 'Upload .apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-android
|
||||
path: |
|
||||
androidApp/build/outputs/apk/**/*.apk
|
||||
androidApp/build/outputs/mapping/**/mapping.txt
|
||||
retention-days: 1
|
||||
dist:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-android-app
|
||||
- build-linux-flatpak-app
|
||||
- build-macos-app
|
||||
- build-windows-app
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: "Generate changelog"
|
||||
id: changelog
|
||||
uses: metcalfc/changelog-generator@v4.2.0
|
||||
with:
|
||||
myToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: "Download Mac app"
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: app-mac
|
||||
path: artifacts
|
||||
- name: "Download Linux app"
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: app-linux-flatpak
|
||||
path: artifacts
|
||||
- name: "Download Windows app"
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: app-windows
|
||||
path: artifacts
|
||||
- name: "Download Android app"
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: app-android
|
||||
path: artifacts
|
||||
- name: "Create release"
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
name: Release ${{ github.ref }}
|
||||
body: ${{ steps.changelog.outputs.changelog }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: |
|
||||
artifacts/*
|
|
@ -0,0 +1,50 @@
|
|||
name: "🕒 Synchronize GPM Credential Privileged Apps"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
# Configuration.
|
||||
- '.github/update_gpm_passkeys_priv_apps.py'
|
||||
- '.github/update_gpm_passkeys_priv_apps.requirements.txt'
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
sync-gpm-passkeys-priv-apps:
|
||||
name: Synchronize GPM Credential Privileged Apps
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install -r .github/update_gpm_passkeys_priv_apps.requirements.txt
|
||||
- name: "Update library"
|
||||
run: |
|
||||
python .github/update_gpm_passkeys_priv_apps.py
|
||||
- name: "Check if any changes"
|
||||
id: check-changes
|
||||
run: |
|
||||
has_changes=$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)
|
||||
echo "$has_changes"
|
||||
echo "{HAS_CHANGES}={$has_changes}" >> "$GITHUB_OUTPUT"
|
||||
- name: Commit and push changes
|
||||
uses: devops-infra/action-commit-push@v0.9.2
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
add_timestamp: false
|
||||
commit_prefix: "[AUTO]"
|
||||
commit_message: "Update GPM Credential Privileged Apps JSON"
|
||||
force: true
|
||||
target_branch: gpmpasskeysprivapps_action
|
||||
- name: Create pull request
|
||||
uses: devops-infra/action-pull-request@v0.5.5
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
source_branch: gpmpasskeysprivapps_action
|
||||
target_branch: master
|
||||
assignee: AChep
|
||||
label: "robot,enhancement"
|
||||
title: New GPM Credential Privileged Apps by GitHub Action
|
|
@ -0,0 +1,50 @@
|
|||
name: "🕒 Synchronize Just delete me"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
# Configuration.
|
||||
- '.github/update_justdeleteme.py'
|
||||
- '.github/update_justdeleteme.requirements.txt'
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
sync-passkeys:
|
||||
name: Synchronize Just delete me
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install -r .github/update_justdeleteme.requirements.txt
|
||||
- name: "Update library"
|
||||
run: |
|
||||
python .github/update_justdeleteme.py
|
||||
- name: "Check if any changes"
|
||||
id: check-changes
|
||||
run: |
|
||||
has_changes=$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)
|
||||
echo "$has_changes"
|
||||
echo "{HAS_CHANGES}={$has_changes}" >> "$GITHUB_OUTPUT"
|
||||
- name: Commit and push changes
|
||||
uses: devops-infra/action-commit-push@v0.9.2
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
add_timestamp: false
|
||||
commit_prefix: "[AUTO]"
|
||||
commit_message: "Update justdeleteme library"
|
||||
force: true
|
||||
target_branch: justdeleteme_action
|
||||
- name: Create pull request
|
||||
uses: devops-infra/action-pull-request@v0.5.5
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
source_branch: justdeleteme_action
|
||||
target_branch: master
|
||||
assignee: AChep
|
||||
label: "robot,enhancement"
|
||||
title: New Just delete me by GitHub Action
|
|
@ -0,0 +1,50 @@
|
|||
name: "🕒 Synchronize Just get my data"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
# Configuration.
|
||||
- '.github/update_justgetmydata.py'
|
||||
- '.github/update_justgetmydata.requirements.txt'
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
sync-passkeys:
|
||||
name: Synchronize Just get my data
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install -r .github/update_justgetmydata.requirements.txt
|
||||
- name: "Update library"
|
||||
run: |
|
||||
python .github/update_justgetmydata.py
|
||||
- name: "Check if any changes"
|
||||
id: check-changes
|
||||
run: |
|
||||
has_changes=$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)
|
||||
echo "$has_changes"
|
||||
echo "{HAS_CHANGES}={$has_changes}" >> "$GITHUB_OUTPUT"
|
||||
- name: Commit and push changes
|
||||
uses: devops-infra/action-commit-push@v0.9.2
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
add_timestamp: false
|
||||
commit_prefix: "[AUTO]"
|
||||
commit_message: "Update justgetmydata library"
|
||||
force: true
|
||||
target_branch: justgetmydata_action
|
||||
- name: Create pull request
|
||||
uses: devops-infra/action-pull-request@v0.5.5
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
source_branch: justgetmydata_action
|
||||
target_branch: master
|
||||
assignee: AChep
|
||||
label: "robot,enhancement"
|
||||
title: New Just get my data by GitHub Action
|
|
@ -0,0 +1,26 @@
|
|||
name: "🌐 Upload Google Play Store listing"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'listing/google/**'
|
||||
# Configuration.
|
||||
- '.github/update_google_play_listing.py'
|
||||
- '.github/update_google_play_listing.requirements.py'
|
||||
|
||||
jobs:
|
||||
sync-google-listing:
|
||||
name: Upload Google Play Store listing
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install -r .github/update_google_play_listing.requirements.txt
|
||||
- name: "Setup secrets"
|
||||
run: |
|
||||
echo ${{ secrets.SERVICE_ACCOUNT_B64 }} | base64 -d | zcat >> service-account-google.json
|
||||
- name: "Update listing"
|
||||
run: |
|
||||
python .github/update_google_play_listing.py
|
|
@ -0,0 +1,35 @@
|
|||
name: "🌐 Synchronize Localization"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'common/src/commonMain/resources/MR/base/*.xml'
|
||||
- 'listing/google/base/*.html'
|
||||
- 'listing/google/base/*.xml'
|
||||
# Configuration.
|
||||
- 'crowdin.yml'
|
||||
|
||||
jobs:
|
||||
sync-localization:
|
||||
name: Synchronize Crowdin Translations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
# We only want to upload the sources, nothing else is
|
||||
# supported.
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
create_pull_request: true
|
||||
pull_request_labels: 'robot,enhancement'
|
||||
pull_request_assignees: 'AChep'
|
||||
config: 'crowdin.yml'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
@ -0,0 +1,50 @@
|
|||
name: "🕒 Synchronize Passkeys"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
# Configuration.
|
||||
- '.github/update_passkeys.py'
|
||||
- '.github/update_passkeys.requirements.txt'
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
sync-passkeys:
|
||||
name: Synchronize Passkeys
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install -r .github/update_passkeys.requirements.txt
|
||||
- name: "Update library"
|
||||
run: |
|
||||
python .github/update_passkeys.py "${{ secrets.PASSKEYS_API_KEY }}"
|
||||
- name: "Check if any changes"
|
||||
id: check-changes
|
||||
run: |
|
||||
has_changes=$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)
|
||||
echo "$has_changes"
|
||||
echo "{HAS_CHANGES}={$has_changes}" >> "$GITHUB_OUTPUT"
|
||||
- name: Commit and push changes
|
||||
uses: devops-infra/action-commit-push@v0.9.2
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
add_timestamp: false
|
||||
commit_prefix: "[AUTO]"
|
||||
commit_message: "Update passkeys library"
|
||||
force: true
|
||||
target_branch: passkeys_action
|
||||
- name: Create pull request
|
||||
uses: devops-infra/action-pull-request@v0.5.5
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
source_branch: passkeys_action
|
||||
target_branch: master
|
||||
assignee: AChep
|
||||
label: "robot,enhancement"
|
||||
title: New Passkeys by GitHub Action
|
|
@ -0,0 +1,43 @@
|
|||
name: "🕒 Synchronize TLD public suffix list"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
sync-tld:
|
||||
name: Synchronize TLD public suffix list
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Update data"
|
||||
run: |
|
||||
wget https://publicsuffix.org/list/public_suffix_list.dat
|
||||
mv -f public_suffix_list.dat common/src/commonMain/resources/MR/files/public_suffix_list.txt
|
||||
- name: "Check if any changes"
|
||||
id: check-changes
|
||||
run: |
|
||||
has_changes=$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)
|
||||
echo "$has_changes"
|
||||
echo "{HAS_CHANGES}={$has_changes}" >> "$GITHUB_OUTPUT"
|
||||
- name: Commit and push changes
|
||||
uses: devops-infra/action-commit-push@v0.9.2
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
add_timestamp: false
|
||||
commit_prefix: "[AUTO]"
|
||||
commit_message: "Update Public suffix list"
|
||||
force: true
|
||||
target_branch: tld_public_suffix_list_action
|
||||
- name: Create pull request
|
||||
uses: devops-infra/action-pull-request@v0.5.5
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
source_branch: tld_public_suffix_list_action
|
||||
target_branch: master
|
||||
assignee: AChep
|
||||
label: "robot,enhancement"
|
||||
title: New Public suffix list by GitHub Action
|
|
@ -0,0 +1,48 @@
|
|||
name: "🕒 Synchronize Two-factor auth"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
# Configuration.
|
||||
- '.github/update_twofactorauth.py'
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
sync-2fa:
|
||||
name: Synchronize Two-factor authentication
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Update library"
|
||||
run: |
|
||||
python .github/update_twofactorauth.py
|
||||
- name: "Check if any changes"
|
||||
id: check-changes
|
||||
run: |
|
||||
has_changes=$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)
|
||||
echo "$has_changes"
|
||||
echo "{HAS_CHANGES}={$has_changes}" >> "$GITHUB_OUTPUT"
|
||||
- name: Commit and push changes
|
||||
uses: devops-infra/action-commit-push@v0.9.2
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
add_timestamp: false
|
||||
commit_prefix: "[AUTO]"
|
||||
commit_message: "Update two-factor auth library"
|
||||
force: true
|
||||
target_branch: tfa_2factorauth_action
|
||||
- name: Create pull request
|
||||
uses: devops-infra/action-pull-request@v0.5.5
|
||||
if: ${{ startsWith(steps.check-changes.outputs.HAS_CHANGES, 'true') }}
|
||||
with:
|
||||
github_token: "${{ secrets.PERSONAL_TOKEN }}"
|
||||
source_branch: tfa_2factorauth_action
|
||||
target_branch: master
|
||||
assignee: AChep
|
||||
label: "robot,enhancement"
|
||||
title: New Two-factor auth by GitHub Action
|
|
@ -0,0 +1,61 @@
|
|||
*.DS_Store
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following line if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
11
README.md
|
@ -14,8 +14,19 @@ _Can be used with any Bitwarden® installation. This product is not associated w
|
|||
#### Highlights:
|
||||
- a beautiful rich **Material You** user interface;
|
||||
- a **powerful** and **fast search**;
|
||||
- a support for creating & using **passkeys** - a modern alternative to passwords.
|
||||
- a watchtower that finds items with **Pwned passwords**, **Vulnerable accounts**, **Reused passwords**, **Inactive two factor authentication**, **Inactive passkeys**, **Unsecure Websites** as well as **Duplicate**, **Incomplete** and **Expiring** items, and other;
|
||||
- **multi-account support** with secure login and two-factor authentication support;
|
||||
- add items, modify, and view your vault **offline**.
|
||||
- beautiful **Light**/**Dark theme**;
|
||||
- and much more!
|
||||
|
||||
#### Looks:
|
||||
| | | |
|
||||
| :----: | :----: | :----: |
|
||||
| ![1](https://github.com/AChep/keyguard-app/blob/master/screenshots/phone/Screenshot_20230928_233006.png) | ![2](https://github.com/AChep/keyguard-app/blob/master/screenshots/phone/Screenshot_20230928_233040.png) | ![3](https://github.com/AChep/keyguard-app/blob/master/screenshots/phone/Screenshot_20230928_233118.png) |
|
||||
| ![4](https://github.com/AChep/keyguard-app/blob/master/screenshots/phone/Screenshot_20230928_233159.png) | ![5](https://github.com/AChep/keyguard-app/blob/master/screenshots/phone/Screenshot_20230928_233236.png) | ![6](https://github.com/AChep/keyguard-app/blob/master/screenshots/phone/Screenshot_20230928_233342.png) |
|
||||
|
||||
## License:
|
||||
|
||||
The source code is available for **personal use** only.
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/build
|
||||
|
||||
# Ignore production keys from being
|
||||
# include into the source system.
|
||||
keyguard-release*
|
||||
# Ignore google-services.json
|
||||
google-services.json
|
|
@ -0,0 +1,2 @@
|
|||
# Disables obfuscation for benchmark builds.
|
||||
-dontobfuscate
|
|
@ -0,0 +1,158 @@
|
|||
import java.io.FileInputStream
|
||||
import java.util.*
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.kapt)
|
||||
alias(libs.plugins.kotlin.plugin.parcelize)
|
||||
alias(libs.plugins.kotlin.plugin.serialization)
|
||||
alias(libs.plugins.compose)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.ktlint)
|
||||
alias(libs.plugins.google.services)
|
||||
alias(libs.plugins.crashlytics)
|
||||
alias(libs.plugins.baseline.profile)
|
||||
}
|
||||
|
||||
fun loadProps(fileName: String): Properties {
|
||||
val props = Properties()
|
||||
val file = file(fileName)
|
||||
if (file.exists()) {
|
||||
var stream: FileInputStream? = null
|
||||
try {
|
||||
stream = file.inputStream()
|
||||
props.load(stream)
|
||||
} finally {
|
||||
stream?.close()
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
val versionInfo = createVersionInfo(
|
||||
marketingVersion = libs.versions.appVersionName.get(),
|
||||
logicalVersion = libs.versions.appVersionCode.get().toInt(),
|
||||
)
|
||||
|
||||
val qaSigningProps = loadProps("keyguard-qa.properties")
|
||||
val releaseSigningProps = loadProps("keyguard-release.properties")
|
||||
|
||||
android {
|
||||
compileSdk = libs.versions.androidCompileSdk.get().toInt()
|
||||
namespace = "com.artemchep.keyguard"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.artemchep.keyguard"
|
||||
minSdk = libs.versions.androidMinSdk.get().toInt()
|
||||
targetSdk = libs.versions.androidTargetSdk.get().toInt()
|
||||
|
||||
versionCode = versionInfo.logicalVersion
|
||||
versionName = versionInfo.marketingVersion
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += listOf(
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
androidResources {
|
||||
@Suppress("UnstableApiUsage")
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
// previous-compilation-data.bin is Gradle's internal machinery for the incremental compilation
|
||||
// that seemed to be packed into the resulting artifact because the lib is depending directly
|
||||
// on the compilation task's output for JPMS/Multi-Release JAR support.
|
||||
//
|
||||
// > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction
|
||||
// > 2 files found with path 'META-INF/versions/9/previous-compilation-data.bin' from inputs:
|
||||
// - /home/runner/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-datetime-jvm/0.4.1/684eec210b21e2da7382a4aa85e56fb7b71f39b3/kotlinx-datetime-jvm-0.4.1.jar
|
||||
// - /home/runner/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/atomicfu-jvm/0.22.0/c6a128a44ba52a18265e5ec816130cd341d80792/atomicfu-jvm-0.22.0.jar
|
||||
packagingOptions {
|
||||
resources.excludes.add("META-INF/versions/9/previous-compilation-data.bin")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
maybeCreate("debug").apply {
|
||||
keyAlias = qaSigningProps.getProperty("key_alias")
|
||||
keyPassword = qaSigningProps.getProperty("password_store")
|
||||
storeFile = file("keyguard-qa.keystore")
|
||||
storePassword = qaSigningProps.getProperty("password_key")
|
||||
}
|
||||
maybeCreate("release").apply {
|
||||
keyAlias = releaseSigningProps.getProperty("key_alias")
|
||||
keyPassword = releaseSigningProps.getProperty("password_store")
|
||||
storeFile = file("keyguard-release.keystore")
|
||||
storePassword = releaseSigningProps.getProperty("password_key")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
maybeCreate("benchmark").apply {
|
||||
initWith(getByName("release"))
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
// Selects release buildType if the 'benchmark'
|
||||
// buildType not available in other modules.
|
||||
matchingFallbacks += "release"
|
||||
// Do not obfuscate
|
||||
proguardFiles(
|
||||
"benchmark-rules.pro",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val accountManagementDimension = "accountManagement"
|
||||
flavorDimensions += accountManagementDimension
|
||||
productFlavors {
|
||||
maybeCreate("playStore").apply {
|
||||
dimension = accountManagementDimension
|
||||
buildConfigField("boolean", "ANALYTICS", "true")
|
||||
}
|
||||
maybeCreate("none").apply {
|
||||
dimension = accountManagementDimension
|
||||
buildConfigField("boolean", "ANALYTICS", "false")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
baselineProfile(project(":androidBenchmark"))
|
||||
coreLibraryDesugaring(libs.android.desugarjdklibs)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(libs.versions.jdk.get().toInt())
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
key_alias=AChep
|
||||
password_store=qqQJQ32ENxi9@ffMVx9crTgQ
|
||||
password_key=qqQJQ32ENxi9@ffMVx9crTgQ
|
|
@ -0,0 +1,115 @@
|
|||
# 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
|
||||
|
||||
##
|
||||
## https://github.com/sqlcipher/sqlcipher-android/issues/18
|
||||
##
|
||||
|
||||
-keep class androidx.room.** extends androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||
|
||||
-keep,includedescriptorclasses class net.zetetic.database.** { *; }
|
||||
-keep,includedescriptorclasses interface net.zetetic.database.** { *; }
|
||||
|
||||
##
|
||||
## https://github.com/Kotlin/kotlinx.serialization#android
|
||||
##
|
||||
|
||||
# Keep `Companion` object fields of serializable classes.
|
||||
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
||||
-if @kotlinx.serialization.Serializable class **
|
||||
-keepclassmembers class <1> {
|
||||
static <1>$Companion Companion;
|
||||
}
|
||||
|
||||
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
|
||||
-if @kotlinx.serialization.Serializable class ** {
|
||||
static **$* *;
|
||||
}
|
||||
-keepclassmembers class <2>$<3> {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Keep `INSTANCE.serializer()` of serializable objects.
|
||||
-if @kotlinx.serialization.Serializable class ** {
|
||||
public static ** INSTANCE;
|
||||
}
|
||||
-keepclassmembers class <1> {
|
||||
public static <1> INSTANCE;
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
|
||||
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
||||
|
||||
# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`.
|
||||
# If you have any, uncomment and replace classes with those containing named companion objects.
|
||||
#-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
|
||||
#-if @kotlinx.serialization.Serializable class
|
||||
#com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
|
||||
#com.example.myapplication.HasNamedCompanion2
|
||||
#{
|
||||
# static **$* *;
|
||||
#}
|
||||
#-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
|
||||
# static <1>$$serializer INSTANCE;
|
||||
#}
|
||||
|
||||
##
|
||||
## kodein
|
||||
##
|
||||
|
||||
-keep, allowobfuscation, allowoptimization class org.kodein.type.TypeReference
|
||||
-keep, allowobfuscation, allowoptimization class org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest
|
||||
|
||||
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.TypeReference
|
||||
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest
|
||||
|
||||
##
|
||||
## java rx
|
||||
##
|
||||
|
||||
# https://github.com/ReactiveX/RxJava#r8-and-proguard-settings
|
||||
-dontwarn java.util.concurrent.Flow*
|
||||
|
||||
##
|
||||
## signalr
|
||||
##
|
||||
|
||||
-keep class com.microsoft.signalr.** { *; }
|
||||
-keep interface com.microsoft.signalr.** { *; }
|
||||
|
||||
##
|
||||
## messagepack
|
||||
##
|
||||
|
||||
-keep class org.msgpack.core.buffer.** { *; }
|
||||
|
||||
##
|
||||
## dont warn
|
||||
##
|
||||
|
||||
-dontwarn edu.umd.cs.findbugs.annotations.**
|
||||
-dontwarn java.sql.JDBCType
|
||||
#-dontwarn okhttp3.internal.platform.**
|
||||
#-dontwarn org.conscrypt.**
|
||||
#-dontwarn org.bouncycastle.jsse**
|
||||
#-dontwarn org.openjsse.**
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">KeyDev</string>
|
||||
</resources>
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<!--
|
||||
Disallow backup.
|
||||
|
||||
Most of the data is encrypted with android device key, so if you
|
||||
restore a backup you can not decrypt it.
|
||||
-->
|
||||
<application
|
||||
android:name=".Main"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_configuration"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Keyguard.Splash">
|
||||
|
||||
<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/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- https://developer.android.com/guide/topics/resources/app-languages#android12-impl -->
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,14 @@
|
|||
<configuration>
|
||||
<appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
|
||||
<tagEncoder>
|
||||
<pattern>%logger{12}</pattern>
|
||||
</tagEncoder>
|
||||
<encoder>
|
||||
<pattern>[%-20thread] %msg</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="logcat" />
|
||||
</root>
|
||||
</configuration>
|
|
@ -0,0 +1,227 @@
|
|||
package com.artemchep.keyguard
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.artemchep.bindin.bindBlock
|
||||
import com.artemchep.keyguard.android.BaseApp
|
||||
import com.artemchep.keyguard.android.downloader.journal.DownloadRepository
|
||||
import com.artemchep.keyguard.android.downloader.worker.AttachmentDownloadAllWorker
|
||||
import com.artemchep.keyguard.android.passkeysModule
|
||||
import com.artemchep.keyguard.billing.BillingManager
|
||||
import com.artemchep.keyguard.billing.BillingManagerImpl
|
||||
import com.artemchep.keyguard.common.AppWorker
|
||||
import com.artemchep.keyguard.common.io.*
|
||||
import com.artemchep.keyguard.common.model.Screen
|
||||
import com.artemchep.keyguard.common.service.logging.LogRepository
|
||||
import com.artemchep.keyguard.common.service.power.PowerService
|
||||
import com.artemchep.keyguard.common.usecase.*
|
||||
import com.artemchep.keyguard.common.usecase.impl.CleanUpAttachmentImpl
|
||||
import com.artemchep.keyguard.core.session.diFingerprintRepositoryModule
|
||||
import com.artemchep.keyguard.common.model.MasterSession
|
||||
import com.artemchep.keyguard.common.service.vault.KeyReadWriteRepository
|
||||
import com.artemchep.keyguard.common.model.PersistedSession
|
||||
import com.artemchep.keyguard.feature.favicon.Favicon
|
||||
import com.artemchep.keyguard.feature.localization.textResource
|
||||
import com.artemchep.keyguard.platform.LeContext
|
||||
import com.artemchep.keyguard.platform.lifecycle.toCommon
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.datetime.Clock
|
||||
import org.kodein.di.*
|
||||
import org.kodein.di.android.x.androidXModule
|
||||
import java.util.*
|
||||
|
||||
class Main : BaseApp(), DIAware {
|
||||
override val di by DI.lazy {
|
||||
import(androidXModule(this@Main))
|
||||
import(diFingerprintRepositoryModule())
|
||||
import(passkeysModule())
|
||||
bind<BillingManager>() with singleton {
|
||||
BillingManagerImpl(
|
||||
context = this@Main,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// See:
|
||||
// https://issuetracker.google.com/issues/243457462
|
||||
override fun attachBaseContext(base: Context) {
|
||||
val updatedContext = ContextCompat.getContextForLanguage(base)
|
||||
|
||||
// Update locale only if needed.
|
||||
val updatedLocale: Locale =
|
||||
updatedContext.resources.configuration.locale
|
||||
if (!Locale.getDefault().equals(updatedLocale)) {
|
||||
Locale.setDefault(updatedLocale)
|
||||
}
|
||||
super.attachBaseContext(updatedContext)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
val logRepository: LogRepository by instance()
|
||||
val getVaultSession: GetVaultSession by instance()
|
||||
val putVaultSession: PutVaultSession by instance()
|
||||
val getVaultPersist: GetVaultPersist by instance()
|
||||
val keyReadWriteRepository: KeyReadWriteRepository by instance()
|
||||
val clearVaultSession: ClearVaultSession by instance()
|
||||
val downloadRepository: DownloadRepository by instance()
|
||||
val cleanUpAttachment: CleanUpAttachment by instance()
|
||||
val appWorker: AppWorker by instance(tag = AppWorker.Feature.SYNC)
|
||||
// val cleanUpDownload: CleanUpDownloadImpl by instance()
|
||||
|
||||
val processLifecycleOwner = ProcessLifecycleOwner.get()
|
||||
val processLifecycle = processLifecycleOwner.lifecycle
|
||||
val processLifecycleFlow = processLifecycle
|
||||
.currentStateFlow
|
||||
// Convert to platform agnostic
|
||||
// lifecycle state.
|
||||
.map { state ->
|
||||
state.toCommon()
|
||||
}
|
||||
processLifecycleOwner.lifecycleScope.launch {
|
||||
appWorker.launch(this, processLifecycleFlow)
|
||||
}
|
||||
|
||||
AttachmentDownloadAllWorker.enqueue(this)
|
||||
|
||||
// attachment clean-up
|
||||
ProcessLifecycleOwner.get().bindBlock {
|
||||
coroutineScope {
|
||||
CleanUpAttachmentImpl.zzz(
|
||||
scope = this,
|
||||
downloadRepository = downloadRepository,
|
||||
cleanUpAttachment = cleanUpAttachment,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// favicon
|
||||
ProcessLifecycleOwner.get().bindBlock {
|
||||
getVaultSession()
|
||||
.map { session ->
|
||||
val key = session as? MasterSession.Key
|
||||
key?.di?.direct?.instance<GetAccounts>()
|
||||
}
|
||||
.collectLatest { getAccounts ->
|
||||
if (getAccounts != null) {
|
||||
coroutineScope {
|
||||
Favicon.launch(this, getAccounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// persist
|
||||
ProcessLifecycleOwner.get().bindBlock {
|
||||
combine(
|
||||
getVaultSession(),
|
||||
getVaultPersist(),
|
||||
) { session, persist ->
|
||||
val persistedMasterKey = if (persist && session is MasterSession.Key) {
|
||||
session.masterKey
|
||||
} else {
|
||||
null
|
||||
}
|
||||
persistedMasterKey
|
||||
}
|
||||
.onEach { masterKey ->
|
||||
val persistedSession = if (masterKey != null) {
|
||||
PersistedSession(
|
||||
masterKey = masterKey,
|
||||
createdAt = Clock.System.now(),
|
||||
persistedAt = Clock.System.now(),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
keyReadWriteRepository.put(persistedSession).bind()
|
||||
}
|
||||
.collect()
|
||||
}
|
||||
|
||||
// timeout
|
||||
var timeoutJob: Job? = null
|
||||
val getVaultLockAfterTimeout: GetVaultLockAfterTimeout by instance()
|
||||
ProcessLifecycleOwner.get().bindBlock {
|
||||
timeoutJob?.cancel()
|
||||
timeoutJob = null
|
||||
|
||||
try {
|
||||
// suspend forever
|
||||
suspendCancellableCoroutine<Unit> { }
|
||||
} finally {
|
||||
timeoutJob = getVaultLockAfterTimeout()
|
||||
.toIO()
|
||||
// Wait for the timeout duration.
|
||||
.effectMap { duration ->
|
||||
delay(duration)
|
||||
duration
|
||||
}
|
||||
.flatMap {
|
||||
// Clear the current session.
|
||||
val context = LeContext(this)
|
||||
val session = MasterSession.Empty(
|
||||
reason = textResource(Res.strings.lock_reason_inactivity, context),
|
||||
)
|
||||
putVaultSession(session)
|
||||
}
|
||||
.attempt()
|
||||
.launchIn(GlobalScope)
|
||||
}
|
||||
}
|
||||
|
||||
// screen lock
|
||||
val getVaultLockAfterScreenOff: GetVaultLockAfterScreenOff by instance()
|
||||
val powerService: PowerService by instance()
|
||||
ProcessLifecycleOwner.get().lifecycleScope.launch {
|
||||
val screenFlow = powerService
|
||||
.getScreenState()
|
||||
.map { screen ->
|
||||
val instant = Clock.System.now()
|
||||
instant to screen
|
||||
}
|
||||
.shareIn(this, SharingStarted.Eagerly, replay = 1)
|
||||
getVaultLockAfterScreenOff()
|
||||
.flatMapLatest { screenLock ->
|
||||
if (screenLock) {
|
||||
getVaultSession()
|
||||
} else {
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
.map { session ->
|
||||
when (session) {
|
||||
is MasterSession.Key -> true
|
||||
is MasterSession.Empty -> false
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.map { sessionExists ->
|
||||
val instant = Clock.System.now()
|
||||
instant to sessionExists
|
||||
}
|
||||
.flatMapLatest { (sessionTimestamp, sessionExists) ->
|
||||
if (sessionExists) {
|
||||
return@flatMapLatest screenFlow
|
||||
.mapNotNull { (screenTimestamp, screen) ->
|
||||
screen
|
||||
.takeIf { screenTimestamp > sessionTimestamp }
|
||||
}
|
||||
}
|
||||
|
||||
emptyFlow()
|
||||
}
|
||||
.filter { it is Screen.Off }
|
||||
.onEach {
|
||||
clearVaultSession()
|
||||
.attempt()
|
||||
.launchIn(this)
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<!-- drawable/apps.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M16,20H20V16H16M16,14H20V10H16M10,8H14V4H10M16,8H20V4H16M10,14H14V10H10M4,14H8V10H4M4,20H8V16H4M10,20H14V16H10M4,8H8V4H4V8Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
|
||||
</vector>
|
|
@ -0,0 +1,3 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/launcher_background" />
|
||||
</shape>
|
|
@ -0,0 +1,38 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M54.5,34L54.5,34A13.5,13.5 0,0 1,68 47.5L68,52.5A13.5,13.5 0,0 1,54.5 66L54.5,66A13.5,13.5 0,0 1,41 52.5L41,47.5A13.5,13.5 0,0 1,54.5 34z"
|
||||
android:strokeWidth="6"
|
||||
android:strokeColor="@color/launcher_handle" />
|
||||
<path
|
||||
android:fillColor="@color/launcher_body_1"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M69,50C71.14,50 72.89,51.68 73,53.8L73,54L73,63.33C73,71.43 66.43,78 58.33,78L50.67,78C42.57,78 36,71.43 36,63.33L36,54C36,51.79 37.79,50 40,50L69,50Z" />
|
||||
<path
|
||||
android:fillColor="@color/launcher_body_2"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M67,50C69.14,50 70.89,51.68 71,53.8L71,54L71,63.33C71,71.43 64.43,78 56.33,78L50.67,78C42.57,78 36,71.43 36,63.33L36,54C36,51.79 37.79,50 40,50L67,50Z" />
|
||||
<path
|
||||
android:fillColor="@color/launcher_body_3"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M67,50C69.14,50 70.89,51.68 71,53.8L71,54L71,63.33C71,71.43 64.43,78 56.33,78L52.67,78C44.57,78 38,71.43 38,63.33L38,54C38,51.79 39.79,50 42,50L67,50Z" />
|
||||
<path
|
||||
android:fillAlpha="0.15675427"
|
||||
android:fillColor="#282828"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M54,56C57.31,56 60,58.35 60,61.25C60,63.34 58.6,65.15 56.57,65.99L56.57,69.5C56.57,70.33 55.9,71 55.07,71L52.93,71C52.1,71 51.43,70.33 51.43,69.5L51.43,65.99C49.4,65.15 48,63.34 48,61.25C48,58.35 50.69,56 54,56Z" />
|
||||
<path
|
||||
android:fillAlpha="0.13071585"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M56,56C59.31,56 62,58.35 62,61.25C62,63.34 60.6,65.15 58.57,65.99L58.57,69.5C58.57,70.33 57.9,71 57.07,71L54.93,71C54.1,71 53.43,70.33 53.43,69.5L53.43,65.99C51.4,65.15 50,63.34 50,61.25C50,58.35 52.69,56 56,56Z" />
|
||||
<path
|
||||
android:fillColor="@color/launcher_handle"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M54.5,56C57.54,56 60,58.35 60,61.25C60,63.34 58.72,65.15 56.86,65.99L56.86,69.5C56.86,70.33 56.19,71 55.36,71L53.64,71C52.81,71 52.14,70.33 52.14,69.5L52.14,65.99C50.28,65.15 49,63.34 49,61.25C49,58.35 51.46,56 54.5,56Z" />
|
||||
</vector>
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:pathData="M56,31C64.28,31 71,37.72 71,46L71,53L65,53L65,46C65,41.12 61.11,37.14 56.27,37L56,37L53,37C48.12,37 44.14,40.89 44,45.73L44,46L44,53L38,53L38,46C38,37.72 44.72,31 53,31L56,31Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#ffffffff"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M67,50C69.14,50 70.89,51.68 71,53.8L71,54L71,63.33C71,71.43 64.43,78 56.33,78L52.67,78C44.57,78 38,71.43 38,63.33L38,54C38,51.79 39.79,50 42,50L67,50ZM54.5,56C51.46,56 49,58.35 49,61.25C49,63.34 50.28,65.15 52.14,65.99L52.14,65.99L52.14,69.5C52.14,70.33 52.81,71 53.64,71L53.64,71L55.36,71C56.19,71 56.86,70.33 56.86,69.5L56.86,69.5L56.86,65.99C58.72,65.15 60,63.34 60,61.25C60,58.35 57.54,56 54.5,56Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#E0F3FF"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
|
@ -0,0 +1,8 @@
|
|||
<!-- drawable/lock_open_outline.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10A2,2 0 0,1 6,8H15V6A3,3 0 0,0 12,3A3,3 0 0,0 9,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,17A2,2 0 0,1 10,15A2,2 0 0,1 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17Z" />
|
||||
</vector>
|
|
@ -0,0 +1,8 @@
|
|||
<!-- drawable/lock_outline.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="640dp"
|
||||
android:height="512dp"
|
||||
android:viewportWidth="640"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#FF777777"
|
||||
android:pathData="M571.2 205.825c-9.082-45.471-30.339-87.624-61.501-121.963-31.159-34.339-71.056-59.575-115.437-73.018-44.377-13.443-91.573-14.586-136.551-3.311s-86.052 34.55-118.84 67.338c-32.788 32.788-56.062 73.862-67.338 118.84s-10.132 92.173 3.311 136.551c13.442 44.381 38.679 84.279 73.018 115.437 34.339 31.161 76.492 52.419 121.964 61.501 41.367 8.409 84.172 6.442 124.594-5.728 40.419-12.166 77.197-34.157 107.046-64.007s51.84-66.627 64.007-107.046c12.169-40.422 14.138-83.228 5.728-124.594zM186.305 76.481c38.576-28.909 85.49-44.517 133.695-44.48 2.752 0 5.376 0.48 8.128 0.608 9.952 2.88 2.368 11.104 2.368 11.104-34.061 17.672-62.726 44.203-82.975 76.8-9.6 15.488-17.216 15.328-19.68 15.040-14.112-0.608-31.456-20.224-44.608-40.16-1.954-2.963-2.723-6.552-2.154-10.055s2.434-6.665 5.226-8.856v0zM253.505 461.184c-1.923 1.971-4.388 3.328-7.083 3.897s-5.497 0.327-8.054-0.697c-40.582-15.911-75.645-43.305-100.902-78.832s-39.611-77.643-41.306-121.199l9.44-13.888c30.432-11.36 60.416-17.504 68.512-10.496 3.744 3.2 1.184 13.12 0 17.216-22.88 74.143 36.288 93.183 58.624 100.383 2.464 0.8 4.576 1.504 6.4 2.112 23.392 9.024 36.736 22.112 39.712 38.848 0.56 11.645-1.404 23.274-5.757 34.087s-10.99 20.56-19.459 28.57h-0.128zM477.152 414.656c-41.667 41.728-98.183 65.226-157.152 65.344-9.798-0.067-19.581-0.807-29.279-2.208-1.884-0.298-3.663-1.063-5.178-2.221-1.514-1.161-2.716-2.678-3.498-4.419s-1.117-3.648-0.976-5.549c0.14-1.901 0.752-3.741 1.779-5.347v0c14.127-18.771 20.486-42.262 17.76-65.6-3.027-13.315-9.63-25.552-19.099-35.392s-21.44-16.909-34.63-20.448c-1.984-0.768-4.544-1.632-7.488-2.56-30.592-9.856-59.168-23.551-44.192-72.447 7.040-22.848 0.256-34.784-6.656-40.768-14.72-12.8-42.976-8.928-68.96-1.216-2.377 0.642-4.879 0.653-7.262 0.034s-4.563-1.849-6.326-3.566c-1.763-1.718-3.049-3.865-3.73-6.231s-0.734-4.868-0.155-7.26c7.821-33.432 23.251-64.608 45.088-91.104 1.379-1.599 3.102-2.864 5.042-3.698s4.043-1.216 6.151-1.118c2.109 0.099 4.167 0.675 6.020 1.688s3.452 2.431 4.674 4.152c14.304 20.224 35.040 42.4 57.6 43.392h1.6c8.108-0.433 15.946-3.062 22.676-7.605s12.098-10.829 15.532-18.188c25.389-38.702 62.678-68.096 106.239-83.744v0c9.504-2.946 19.648-3.124 29.248-0.512 29.693 11.669 56.55 29.538 78.784 52.416 1.798 1.852 3.053 4.159 3.635 6.674s0.467 5.138-0.333 7.591c-0.8 2.453-2.256 4.641-4.208 6.329s-4.327 2.81-6.87 3.247c-41.088 7.136-94.208 21.184-101.088 46.816-3.2 12.416 2.912 24.48 18.56 35.872 60.288 43.744 77.984 77.344 68.768 91.232-8.49 10.898-14.24 23.673-16.774 37.254-2.534 13.577-1.773 27.568 2.214 40.793 5.907 10.624 15.488 18.726 26.944 22.784 0 0 11.36 6.528 5.664 15.584h-0.128zM512 369.664c-2.966 3.639-6.743 6.531-11.024 8.448-4.285 1.917-8.96 2.807-13.648 2.592-4.595-0.729-8.992-2.409-12.909-4.925-3.917-2.519-7.267-5.821-9.843-9.699-7.584-14.624 2.432-38.944 13.312-55.359 11.296-17.12 16.736-55.68-74.304-121.76-7.648-5.536-10.592-10.080-10.080-11.936 3.2-11.808 52.608-27.072 108.8-34.336 2.845-0.366 5.735 0.123 8.298 1.405 2.567 1.282 4.691 3.299 6.102 5.794 18.557 33.828 27.891 71.942 27.066 110.518s-11.783 76.255-31.769 109.257v0z" />
|
||||
</vector>
|
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M11,7H13A2,2 0 0,1 15,9V15A2,2 0 0,1 13,17H11A2,2 0 0,1 9,15V9A2,2 0 0,1 11,7M11,9V15H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M10,7H14V17H12V9H10V7M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M9,7H13A2,2 0 0,1 15,9V11A2,2 0 0,1 13,13H11V15H15V17H11L9,17V13A2,2 0 0,1 11,11H13V9H9V7M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M15,15A2,2 0 0,1 13,17H9V15H13V13H11V11H13V9H9V7H13A2,2 0 0,1 15,9V10.5A1.5,1.5 0 0,1 13.5,12A1.5,1.5 0 0,1 15,13.5V15M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M9,7H11V11H13V7H15V17H13V13H9V7M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M9,7H15V9H11V11H13A2,2 0 0,1 15,13V15A2,2 0 0,1 13,17H9V15H13V13H9V7M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M11,7H15V9H11V11H13A2,2 0 0,1 15,13V15A2,2 0 0,1 13,17H11A2,2 0 0,1 9,15V9A2,2 0 0,1 11,7M11,13V15H13V13H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M11,17H9L13,9H9V7H15V9L11,17M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M11,13V15H13V13H11M11,9V11H13V9H11M11,17A2,2 0 0,1 9,15V13.5A1.5,1.5 0 0,1 10.5,12A1.5,1.5 0 0,1 9,10.5V9A2,2 0 0,1 11,7H13A2,2 0 0,1 15,9V10.5A1.5,1.5 0 0,1 13.5,12A1.5,1.5 0 0,1 15,13.5V15A2,2 0 0,1 13,17H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M13,17H9V15H13V13H11A2,2 0 0,1 9,11V9A2,2 0 0,1 11,7H13A2,2 0 0,1 15,9V15A2,2 0 0,1 13,17M13,11V9H11V11H13M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M19,11V13H17V15H15V13H13V11H15V9H17V11H19M10,7A2,2 0 0,1 12,9V15C12,16.11 11.1,17 10,17H6V15H10V13H8A2,2 0 0,1 6,11V9C6,7.89 6.9,7 8,7H10M8,9V11H10V9H8M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</vector>
|
|
@ -0,0 +1,4 @@
|
|||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_launcher_background" />
|
||||
<item android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</layer-list>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z" />
|
||||
</vector>
|
|
@ -0,0 +1,8 @@
|
|||
<!-- drawable/web.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M16.36,14C16.44,13.34 16.5,12.68 16.5,12C16.5,11.32 16.44,10.66 16.36,10H19.74C19.9,10.64 20,11.31 20,12C20,12.69 19.9,13.36 19.74,14M14.59,19.56C15.19,18.45 15.65,17.25 15.97,16H18.92C17.96,17.65 16.43,18.93 14.59,19.56M14.34,14H9.66C9.56,13.34 9.5,12.68 9.5,12C9.5,11.32 9.56,10.65 9.66,10H14.34C14.43,10.65 14.5,11.32 14.5,12C14.5,12.68 14.43,13.34 14.34,14M12,19.96C11.17,18.76 10.5,17.43 10.09,16H13.91C13.5,17.43 12.83,18.76 12,19.96M8,8H5.08C6.03,6.34 7.57,5.06 9.4,4.44C8.8,5.55 8.35,6.75 8,8M5.08,16H8C8.35,17.25 8.8,18.45 9.4,19.56C7.57,18.93 6.03,17.65 5.08,16M4.26,14C4.1,13.36 4,12.69 4,12C4,11.31 4.1,10.64 4.26,10H7.64C7.56,10.66 7.5,11.32 7.5,12C7.5,12.68 7.56,13.34 7.64,14M12,4.03C12.83,5.23 13.5,6.57 13.91,8H10.09C10.5,6.57 11.17,5.23 12,4.03M18.92,8H15.97C15.65,6.75 15.19,5.55 14.59,4.44C16.43,5.07 17.96,6.34 18.92,8M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/lightgray"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:maxWidth="20dp"
|
||||
android:maxHeight="20dp"
|
||||
android:src="@drawable/ic_login" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/black"
|
||||
tools:text="Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/gray"
|
||||
tools:text="Username" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/launcher_background"
|
||||
android:minHeight="24dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/autofill_app_id_icon"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:contentDescription="@string/about"
|
||||
android:src="@drawable/ic_apps"
|
||||
android:tint="@color/launcher_body_2"
|
||||
tools:ignore="UseAppTint" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/autofill_app_id_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textColor="@color/launcher_body_2"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/autofill_entry_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:src="@drawable/ic_web"
|
||||
android:tint="?android:colorControlNormal"
|
||||
tools:ignore="UseAppTint" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/autofill_entry_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/autofill_entry_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.6"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:contentDescription="@string/autofill_open_keyguard"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:text="@string/autofill_open_keyguard"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/item_autofill_app_id" />
|
||||
|
||||
<include layout="@layout/item_autofill_select_entry" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/item_autofill_web_domain" />
|
||||
|
||||
<include layout="@layout/item_autofill_select_entry" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:text="@string/autofill_unlock_keyguard"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/item_autofill_app_id" />
|
||||
|
||||
<include layout="@layout/item_autofill_unlock" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/item_autofill_web_domain" />
|
||||
|
||||
<include layout="@layout/item_autofill_unlock" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/launcher_background"
|
||||
android:minHeight="24dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/autofill_web_domain_icon"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:src="@drawable/ic_web"
|
||||
android:tint="@color/launcher_body_2"
|
||||
tools:ignore="UseAppTint" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/autofill_web_domain_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textColor="@color/launcher_body_2"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1 @@
|
|||
unqualifiedResLocale=en-US
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="launcher_background">@android:color/system_accent1_400</color>
|
||||
<color name="launcher_handle">@android:color/system_neutral1_900</color>
|
||||
<color name="launcher_body_1">@android:color/system_accent1_100</color>
|
||||
<color name="launcher_body_2">@android:color/system_accent2_50</color>
|
||||
<color name="launcher_body_3">@android:color/system_accent2_50</color>
|
||||
</resources>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="launcher_background">#FF219BCC</color>
|
||||
<color name="launcher_handle">#FF191C1E</color>
|
||||
<color name="launcher_body_1">#FFC1E8FF</color>
|
||||
<color name="launcher_body_2">#FFE0F3FF</color>
|
||||
<color name="launcher_body_3">#FFE0F3FF</color>
|
||||
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="green">#43a047</color>
|
||||
|
||||
<!-- Other -->
|
||||
<color name="darkgray">#333333</color>
|
||||
<color name="gray">#738182</color>
|
||||
<color name="lightgray">#efeff4</color>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<resources>
|
||||
<item name="notification_attachment_upload_id" type="integer">1100</item>
|
||||
<item name="notification_attachment_download_id" type="integer">1200</item>
|
||||
</resources>
|
|
@ -0,0 +1,23 @@
|
|||
<resources>
|
||||
<string name="app_name">Keyguard</string>
|
||||
|
||||
<string name="notification_attachment_upload_channel_id">com.artemchep.keyguard.UPLOAD_ATTACHMENTS</string>
|
||||
<string name="notification_attachment_upload_channel_name">Uploading attachments</string>
|
||||
<string name="notification_attachment_upload_title">Uploading attachments</string>
|
||||
<string name="notification_attachment_upload_content">Uploading attachments</string>
|
||||
|
||||
<string name="notification_attachment_download_channel_id">com.artemchep.keyguard.DOWNLOAD_ATTACHMENTS</string>
|
||||
<string name="notification_attachment_download_channel_name">Download attachments</string>
|
||||
|
||||
<string name="notification_clipboard_channel_id">com.artemchep.keyguard.CLIPBOARD</string>
|
||||
<string name="notification_clipboard_channel_name">Clipboard</string>
|
||||
|
||||
<string name="about">About</string>
|
||||
<string name="autofill">Autofill</string>
|
||||
<string name="autofill_service_name">Keyguard form autofilling</string>
|
||||
<string name="autofill_sign_in_prompt">Unlock Keyguard</string>
|
||||
<string name="autofill_explanation_summary">Enable autofilling to quickly fill out forms in other apps</string>
|
||||
<string name="autofill_select_entry">Open Keyguard</string>
|
||||
<string name="set_autofill_service_title">Set default autofill service</string>
|
||||
<string name="autofill_preference_title">Autofill settings</string>
|
||||
</resources>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.Keyguard.Splash" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash</item>
|
||||
<item name="windowSplashScreenAnimationDuration">0</item>
|
||||
|
||||
<item name="postSplashScreenTheme">@style/Theme.Keyguard</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Keyguard" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,15 @@
|
|||
package com.artemchep.keyguard
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() = runBlocking {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import com.android.build.api.dsl.ManagedVirtualDevice
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.test)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.baseline.profile)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = libs.versions.androidCompileSdk.get().toInt()
|
||||
namespace = "com.artemchep.macrobenchmark"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.androidMinSdk.get().toInt()
|
||||
targetSdk = libs.versions.androidTargetSdk.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
testOptions.managedDevices.devices {
|
||||
maybeCreate<ManagedVirtualDevice>("pixel6api34").apply {
|
||||
device = "Pixel 6"
|
||||
apiLevel = 34
|
||||
systemImageSource = "google_apis_playstore"
|
||||
}
|
||||
}
|
||||
|
||||
targetProjectPath = ":androidApp"
|
||||
// Enable the benchmark to run separately from the app process
|
||||
experimentalProperties["android.experimental.self-instrumenting"] = true
|
||||
|
||||
buildTypes {
|
||||
// declare a build type to match the target app"s build type
|
||||
create("benchmark") {
|
||||
isDebuggable = true
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
// Selects release buildType if the 'benchmark'
|
||||
// buildType not available in other modules.
|
||||
matchingFallbacks.add("release")
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
val accountManagementDimension = "accountManagement"
|
||||
flavorDimensions += accountManagementDimension
|
||||
productFlavors {
|
||||
maybeCreate("playStore").apply {
|
||||
dimension = accountManagementDimension
|
||||
}
|
||||
maybeCreate("none").apply {
|
||||
dimension = accountManagementDimension
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baselineProfile {
|
||||
managedDevices += "pixel6api34"
|
||||
|
||||
// This enables using connected devices to generate profiles. The default is true.
|
||||
// When using connected devices, they must be rooted or API 33 and higher.
|
||||
useConnectedDevices = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.benchmark.macro.junit4)
|
||||
implementation(libs.androidx.espresso.core)
|
||||
implementation(libs.androidx.junit)
|
||||
implementation(libs.androidx.uiautomator)
|
||||
implementation(libs.androidx.profileinstaller)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Requesting legacy storage is needed to be able to write to additionalTestOutputDir on API 29 -->
|
||||
<application android:requestLegacyExternalStorage="true" />
|
||||
|
||||
<queries>
|
||||
<package android:name="com.artemchep.keyguard" />
|
||||
</queries>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,15 @@
|
|||
package com.artemchep.macrobenchmark
|
||||
|
||||
/**
|
||||
* Convenience parameter to use proper package name
|
||||
* with regards to build type and build flavor.
|
||||
*/
|
||||
val PACKAGE_NAME = StringBuilder("com.artemchep.keyguard").apply {
|
||||
val hasSuffix = when (BuildConfig.BUILD_TYPE) {
|
||||
"debug" -> true
|
||||
else -> false
|
||||
}
|
||||
if (hasSuffix) {
|
||||
append(".${BuildConfig.BUILD_TYPE}")
|
||||
}
|
||||
}.toString()
|
|
@ -0,0 +1,33 @@
|
|||
package com.artemchep.macrobenchmark.baselineprofile
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
||||
import com.artemchep.macrobenchmark.PACKAGE_NAME
|
||||
import com.artemchep.macrobenchmark.ui.keyguard.createVaultAndWait
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Generates a baseline profile which
|
||||
* can be copied to `app/src/main/baseline-prof.txt`.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
class BaselineProfileGenerator {
|
||||
@get:Rule
|
||||
val baselineProfileRule = BaselineProfileRule()
|
||||
|
||||
@Test
|
||||
fun generate() = baselineProfileRule.collect(
|
||||
packageName = PACKAGE_NAME,
|
||||
) {
|
||||
// This block defines the app's critical user journey. Here we are interested in
|
||||
// optimizing for app startup. But you can also navigate and scroll
|
||||
// through your most important UI.
|
||||
|
||||
pressHome()
|
||||
startActivityAndWait()
|
||||
|
||||
createVaultAndWait()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.artemchep.macrobenchmark.ui.keyguard
|
||||
|
||||
import androidx.benchmark.macro.MacrobenchmarkScope
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Until
|
||||
import java.util.regex.Pattern
|
||||
|
||||
fun MacrobenchmarkScope.waitForMainScreen() = kotlin.run {
|
||||
val search = kotlin.run {
|
||||
val pattern = Pattern.compile("nav:(setup|unlock|main)")
|
||||
val selector = By.res(pattern)
|
||||
Until.findObject(selector)
|
||||
}
|
||||
device.wait(search, 30_000)
|
||||
}
|
||||
|
||||
fun MacrobenchmarkScope.createVaultAndWait() = kotlin.run {
|
||||
val screen = waitForMainScreen()
|
||||
when (screen.resourceName) {
|
||||
"nav:setup",
|
||||
"nav:unlock",
|
||||
-> {
|
||||
// Create or unlock existing vault
|
||||
val password = screen
|
||||
.findObject(By.res("field:password"))
|
||||
password.text = "111111"
|
||||
val btn = screen
|
||||
.findObject(By.res("btn:go"))
|
||||
btn.wait(Until.clickable(true), 1000L)
|
||||
btn.click()
|
||||
// wait till main screen is loaded
|
||||
device.wait(Until.findObject(By.res("nav:main")), 30_000)
|
||||
}
|
||||
else -> screen
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 21 KiB |