This commit is contained in:
Max Mushkin 2020-09-07 23:46:59 +03:00
Родитель 11efcaa703
Коммит 3f54c41ede
271 изменённых файлов: 13823 добавлений и 22 удалений

75
.gitignore поставляемый
Просмотреть файл

@ -1,23 +1,54 @@
# Compiled class file
# Windows thumbnail db
Thumbs.db
# OSX files
.DS_Store
# built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# generated files
bin/
gen/
build/
# Local configuration file (sdk path, etc)
local.properties
# Eclipse project files
.classpath
.project
# Android Studio
.gradle
/*/local.properties
/*/out
/*/*/build
build
/*/*/production
*.iml
*.iws
*.ipr
*~
*.swp
/.idea/libraries
/.idea/caches
/.idea/gradle.xml
/.idea/modules.xml
/.idea/workspace.xml
/.idea/tasks.xml
/.idea/vcs.xml
/build
/captures
.externalNativeBuild
*.orig
.idea/.name
.idea/jarRepositories.xml
.idea/navEditor.xml

Просмотреть файл

@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="imageWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="imageAssetPanel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="actionbar">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\dunajczykb\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="launcher">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="C:\Users\dunajczykb\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="D:\Projects\Microsoft\Dynamics 365\Graphics\ic_icon.png" />
<entry key="scalingPercent" value="46" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="backgroundAssetType" value="COLOR" />
<entry key="backgroundColor" value="ffffff" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="launcherLegacy">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\dunajczykb\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="notification">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\dunajczykb\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvBanner">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvChannel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="C:\Users\dunajczykb\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>

Просмотреть файл

@ -0,0 +1,122 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

Просмотреть файл

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

8
.idea/compiler.xml Normal file
Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
</annotationProcessing>
</component>
</project>

4
.idea/encodings.xml Normal file
Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

9
.idea/misc.xml Normal file
Просмотреть файл

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

Просмотреть файл

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

1
app/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
/build

110
app/build.gradle Normal file
Просмотреть файл

@ -0,0 +1,110 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'androidx.navigation.safeargs'
android {
signingConfigs {
ttpsctemprelease {
storeFile file('..\\ttpsctemprelease.jks')
storePassword 'Password'
keyPassword 'Password'
keyAlias 'ttpsctemprelease'
}
}
compileSdkVersion 29
buildToolsVersion "29.0.2"
kapt {
generateStubs = true
}
defaultConfig {
applicationId "com.ttpsc.com.ttpsc.dynamics365fieldService"
minSdkVersion 26
targetSdkVersion 29
versionCode 10
versionName "1.0 b10"
renderscriptTargetApi 29
renderscriptSupportModeEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.ttpsctemprelease
}
}
dataBinding {
enabled = true
}
// To inline the bytecode built with JVM target 1.8 into
// bytecode that is being built with JVM target 1.6. (e.g. navArgs)
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.71"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.0'
implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
implementation "com.github.akarnokd:rxjava3-retrofit-adapter:3.0.0"
implementation 'com.google.dagger:dagger-android:2.27'
implementation 'com.google.dagger:dagger-android-support:2.27'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.27'
annotationProcessor 'com.google.dagger:dagger-compiler:2.27'
kapt 'com.google.dagger:dagger-compiler:2.27'
kapt 'com.google.dagger:dagger-android-processor:2.27'
compileOnly 'javax.annotation:jsr250-api:1.0'
implementation 'javax.inject:javax.inject:1'
implementation "android.arch.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "android.arch.navigation:navigation-ui-ktx:$navigationVersion"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.5.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
def appCenterSdkVersion = '3.2.2'
implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}"
implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
implementation "androidx.security:security-crypto:1.1.0-alpha01"
implementation 'org.conscrypt:conscrypt-android:2.2.1'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.microsoft.identity.client:msal:1.4.+'
implementation 'com.googlecode.mp4parser:isoparser:1.1.22'
implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
}

21
app/proguard-rules.pro поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
# 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

20
app/release/output.json Normal file
Просмотреть файл

@ -0,0 +1,20 @@
{
"version": 1,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.ttpsc.com.ttpsc.dynamics365fieldService",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"properties": [],
"versionCode": 2,
"versionName": "2",
"enabled": true,
"outputFile": "app-release.apk"
}
]
}

Просмотреть файл

@ -0,0 +1,21 @@
package com.ttpsc.dynamics365fieldService
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.ttpsc.com.ttpsc.dynamics365fieldService", appContext.packageName)
}
}

Просмотреть файл

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ttpsc.dynamics365fieldService">
<uses-feature
android:name="android.hardware.sensor.gyroscope"
android:required="true" />
<uses-feature android:name="android.hardware.camera.any"
android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus"
android:required="true" />
<!--Permissions-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:name=".AndroidApplication"
android:allowBackup="true"
android:fullBackupContent="@xml/appcenter_backup_rule"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".views.activities.MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="dynamicsfcs" />
<data android:host="workorders" />
<data android:pathPrefix="/" />
</intent-filter>
</activity>
<activity android:name="com.microsoft.identity.client.BrowserTabActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="com.ttpsc.dynamics365fieldService"
android:path="/Bmce+9aHdOoVtE7fS3B07tfj7Bc="
android:scheme="msauth" />
</intent-filter>
</activity>
<activity android:name="com.ttpsc.dynamics365fieldService.views.activities.RecordingActivity"
android:theme="@style/AppTheme.NoActionBar">
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>

Двоичные данные
app/src/main/ic_guides-playstore.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 40 KiB

Двоичные данные
app/src/main/ic_launcher-playstore.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 175 KiB

Просмотреть файл

@ -0,0 +1,28 @@
package com.ttpsc.dynamics365fieldService
import android.app.Application
import com.ttpsc.dynamics365fieldService.core.di.ApplicationComponent
import com.ttpsc.dynamics365fieldService.core.di.ApplicationModule
import com.ttpsc.dynamics365fieldService.core.di.DaggerApplicationComponent
class AndroidApplication : Application() {
companion object {
var companionAppComponent: ApplicationComponent? = null
}
private val appComponent: ApplicationComponent by lazy(mode = LazyThreadSafetyMode.NONE) {
DaggerApplicationComponent
.builder()
.applicationModule(ApplicationModule(this))
.build()
}
override fun onCreate() {
super.onCreate()
this.injectMembers()
companionAppComponent = appComponent
}
private fun injectMembers() = appComponent.inject(this)
}

Просмотреть файл

@ -0,0 +1,31 @@
package com.ttpsc.dynamics365fieldService
class AppConfiguration {
companion object {
val ENDPOINT_SUFFIX = "/api/data/v9.0/"
private var _endpoint = ""
var endpoint: String
get() = _endpoint
set(value) {
_endpoint = value
}
}
class StorageKeys {
companion object {
val environmentKey = "ENVIRONMENT_KEY"
val userAccountKey = "USER_ACCOUNT_KEY"
val accessTokenKey = "TOKEN_STORAGE_KEY"
val userResourceIdKey = "USER_RESOURCE_ID_KEY"
val accessTokenExpirationDateKey = "ACCESS_TOKEN_EXPIRATION_DATE_KEY"
val userFirstNameKey = "USER_FIRST_NAME_KEY"
val userLastNameKey = "USER_LAST_NAME_KEY"
}
}
class RequestTags{
companion object {
val leaveUrl = "LEAVE_URL"
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction
import io.reactivex.rxjava3.core.Observable
interface Operation<TResult> {
fun execute(): Observable<TResult>
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
interface ChangeBookableResourceBookingStatusOperation: Operation<Pair<Boolean, String?>> {
var bookingStatusId: String
var bookableResourceBookingId: String
}

Просмотреть файл

@ -0,0 +1,12 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
interface CreateNoteOperation: Operation<Pair<Boolean, String?>> {
var filename: String?
var subject: String?
var isDocument: Boolean
var objecttypecode: String
var workOrderId: String?
var documentBody: Array<Byte>?
}

Просмотреть файл

@ -0,0 +1,9 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
interface CreateProcedureOperation : Operation<Procedure> {
var title: String
var subject: String?
}

Просмотреть файл

@ -0,0 +1,15 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.DescriptionLine
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
import java.io.File
interface CreateStepOperation : Operation<Procedure> {
var guideId: String
var stepNumber: Int
var descriptionLines: List<DescriptionLine>
var title: String
var images: List<File>?
var video: File?
}

Просмотреть файл

@ -0,0 +1,7 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
interface DownloadFileOperation : Operation<ByteArray> {
var fileUrl: String
}

Просмотреть файл

@ -0,0 +1,9 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.Alert
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
interface GetAlertsOperation : Operation<List<Alert>>{
var queryMap: Map<String, String>?
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResourceBooking
interface GetBookableResourceBookingDetailsOperation: Operation<BookableResourceBooking> {
var bookableResourceBookingId: String
}

Просмотреть файл

@ -0,0 +1,9 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
interface GetBookableResourceBookingsCountOperation : Operation<Int> {
var bookableResource: BookableResource
var query: Map<String, String>?
}

Просмотреть файл

@ -0,0 +1,13 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
import com.ttpsc.dynamics365fieldService.bll.models.BookableResourceBooking
import com.ttpsc.dynamics365fieldService.bll.models.PageableModel
interface GetBookableResourcesBookingsOperation :
Operation<PageableModel<List<BookableResourceBooking>>> {
var bookableResource: BookableResource
var query: Map<String, String>?
var itemsPerPage: Int?
}

Просмотреть файл

@ -0,0 +1,7 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
import com.ttpsc.dynamics365fieldService.bll.models.BookingStatus
interface GetBookableResourcesOperation : Operation<BookableResource>

Просмотреть файл

@ -0,0 +1,6 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.BookingStatus
interface GetBookingStatusesOperation : Operation<List<BookingStatus>>

Просмотреть файл

@ -0,0 +1,6 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.Category
interface GetCategoriesOperation : Operation<MutableList<Category>>

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.CustomField
interface GetFormOperation: Operation<List<CustomField>> {
var query: Map<String, String>?
}

Просмотреть файл

@ -0,0 +1,9 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.Note
import com.ttpsc.dynamics365fieldService.bll.models.WorkOrder
interface GetNotesOperation : Operation<List<Note>> {
var queryMap: Map<String, String>?
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
interface GetProcedureOperation : Operation<Procedure> {
var procedureId: String
}

Просмотреть файл

@ -0,0 +1,10 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
interface GetProceduresOperation : Operation<List<Procedure>>{
var bookableResource: BookableResource
var query: Map<String, String>?
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.Note
interface GetRawWorkOrdersOperation : Operation<Pair<Map<String, String>, String?>> {
var queryMap: Map<String, String>?
}

Просмотреть файл

@ -0,0 +1,9 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
import com.ttpsc.dynamics365fieldService.bll.models.UserInfo
interface GetUserInfoOperation : Operation<List<UserInfo>>{
var email: String
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
import com.ttpsc.dynamics365fieldService.bll.models.WorkOrder
interface GetWorkOrdersOperation : Operation<List<WorkOrder>>{
var queryMap: Map<String, String>?
}

Просмотреть файл

@ -0,0 +1,13 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import android.app.Activity
import android.content.Context
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
interface LogInOperation : Operation<Boolean> {
var userName: String?
var environmentUrl: String?
var activity: Activity?
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
interface SaveEnvironmentDataOperation: Operation<Unit> {
var environmentUrl: String?
var userEmail: String?
}

Просмотреть файл

@ -0,0 +1,8 @@
package com.ttpsc.dynamics365fieldService.bll.abstraction.operations
import android.app.Activity
import com.ttpsc.dynamics365fieldService.bll.abstraction.Operation
interface UpdateUserSessionOperation: Operation<Unit> {
var activity: Activity?
}

Просмотреть файл

@ -0,0 +1,25 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.io.Serializable
class Alert (
val alertId: String?,
val building: String?,
val caseOwner: String?,
val caseOwnerId: String?,
val description: String?,
val deviceName: String?,
val equipment: String?,
val faultSavings: Float?,
val floor: String?,
val fullAssetPath: String?,
val stateCode: Int?,
val ioTHubDeviceId: String?,
val latitude: Float?,
val likelihood: Float?,
val longitude: Float?,
val subject: String?,
val unit: String?,
val equipmentClass: String?,
val workOrderId: String?
): Serializable

Просмотреть файл

@ -0,0 +1,24 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.io.Serializable
data class Attachment(
val fileUrl: String?,
val filePath: String?,
val customFileName: String?
) : Serializable {
var thumbnailUrl: String? = ""
var mediumUrl: String? = ""
var attachmentType: AttachmentType = AttachmentType.NONE
@Suppress("unused")
val fileName: String?
get() {
val fileUri = filePath ?: fileUrl
return fileUri?.split('/')?.last() ?: customFileName
}
}
enum class AttachmentType : Serializable {
NONE, IMAGE, VIDEO
}

Просмотреть файл

@ -0,0 +1,6 @@
package com.ttpsc.dynamics365fieldService.bll.models
class BookableResource(
val userId: String,
val bookableResourceId: String
)

Просмотреть файл

@ -0,0 +1,55 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.util.*
class BookableResourceBooking(
var id: String?,
var workOrderId: String?,
var workOrder: WorkOrder?,
var startTime: Date?,
var endTime: Date?,
var resource: String?,
var bookingStatus: String?,
var name: String?,
var stateCode: Int?,
var statusCode: Int?,
var latitude: String?,
var longitude: String?
)
{
var nextPageLink: String? = null
fun toProcedure(): Procedure{
val procedure = Procedure(
id = this.id!!,
key = this.id!!,
guid = "",
title = workOrder?.summary,
categoryName = "",
descriptionLines = mutableListOf(DescriptionLine(
textRaw = workOrder?.iotAlerts?.firstOrNull()?.description ?: "",
bullet = TextBullet.None,
level = 0,
lineId = 0
)),
labels = listOf(),
template = null,
parent = null,
children = null,
timeSpentMillis = 0,
timeEstimateMillis = 0,
attachments = mutableListOf(),
isTemplate = false,
isSynchronizable = false,
level = "",
alerts = workOrder?.iotAlerts ?: mutableListOf(),
startTime = this.startTime,
endTime = this.endTime,
status = bookingStatus,
workOrderId = workOrder?.id,
workOrder = workOrder
)
return procedure
}
}

Просмотреть файл

@ -0,0 +1,11 @@
package com.ttpsc.dynamics365fieldService.bll.models
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import java.io.Serializable
@Parcelize
class BookingStatus(
val name: String,
var bookingStatusId: String? = null
) : Parcelable, Serializable

Просмотреть файл

@ -0,0 +1,17 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.io.Serializable
data class Category(
val id: String,
val key: String,
val guid: String?,
val title: String,
val description: String,
val labels: List<String>?,
var procedures: List<Procedure>?,
val timeSpentMillis: Int = 0,
val timeEstimateMillis: Int = 0,
val attachments: List<Attachment>?,
val isSynchronizable: Boolean = false
) : Serializable

Просмотреть файл

@ -0,0 +1,10 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.io.Serializable
class CustomField(
var fieldName: String,
var fieldDescription: String,
var fieldValue: String
) : Serializable {
}

Просмотреть файл

@ -0,0 +1,27 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.io.Serializable
data class DescriptionLine(
val textRaw: String,
val bullet: TextBullet?,
val level: Int?,
val lineId: Any?
) : Serializable {
override fun toString() = textRaw
}
enum class TextBullet : Serializable {
None,
Black,
Red,
Orange,
Yellow,
LightBlue,
Blue,
Green,
Violet,
IconNote,
IconCaution,
IconReminder
}

Просмотреть файл

@ -0,0 +1,19 @@
package com.ttpsc.dynamics365fieldService.bll.models
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import java.io.Serializable
import java.util.*
@Parcelize
class Note (
val id: String?,
val fileName: String?,
val subject: String?,
val isDocument:Boolean,
val associatedTo: String?,
val date: Date?
): Parcelable, Serializable {
lateinit var documentBody: Array<Byte>
var userInfo: UserInfo? = null
}

Просмотреть файл

@ -0,0 +1,18 @@
package com.ttpsc.dynamics365fieldService.bll.models
import com.google.gson.annotations.SerializedName
data class OauthResult(
@SerializedName("token_type")
val tokenType:String,
@SerializedName("scope")
val scope: String,
@SerializedName("expires_in")
val expirationSeconds: Int,
@SerializedName("access_token")
val access_token: String,
@SerializedName("refresh_token")
val refresh_token: String,
@SerializedName("id_token")
val id_token: String
)

Просмотреть файл

@ -0,0 +1,6 @@
package com.ttpsc.dynamics365fieldService.bll.models
class PageableModel<ModelType> (
val model: ModelType,
val nextPageLink: String?
)

Просмотреть файл

@ -0,0 +1,32 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.io.Serializable
import java.util.*
data class Procedure(
val id: String,
val key: String,
val guid: String?,
var title: String?,
val categoryName: String,
val descriptionLines: List<DescriptionLine>,
val labels: List<String>?,
val template: Procedure?,
val parent: Procedure?,
var children: List<Procedure>?,
val timeSpentMillis: Int = 0,
val timeEstimateMillis: Int = 0,
val attachments: MutableList<Attachment>?,
val isTemplate: Boolean = true,
val isSynchronizable: Boolean = false,
val level: String,
val alerts: MutableList<Alert> = mutableListOf(),
val startTime: Date?,
val endTime: Date?,
val status: String?,
val workOrderId: String?,
var workOrder: WorkOrder?
) : Serializable {
var customFields: MutableList<CustomField> = mutableListOf()
override fun toString() = "$id | $title"
}

Просмотреть файл

@ -0,0 +1,11 @@
package com.ttpsc.dynamics365fieldService.bll.models
import com.google.gson.annotations.SerializedName
import java.io.Serializable
data class QrCodeScan (
@SerializedName("ResourceId")
var environmentUrl: String,
@SerializedName("UserId")
val login: String
): Serializable {}

Просмотреть файл

@ -0,0 +1,30 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.io.Serializable
class ServiceTerritory(
val odata_etag: String? = null,
val entityimageid: String? = null,
val timezoneruleversionnumber: String? = null,
val modifiedon: String? = null,
val importsequencenumber: String? = null,
val _parentterritoryid_value: String? = null,
val _managerid_value: String? = null,
val _createdonbehalfby_value: String? = null,
val _createdby_value: String? = null,
val _modifiedonbehalfby_value: String? = null,
val entityimage_timestamp: String? = null,
val exchangerate: String? = null,
val description: String? = null,
val utcconversiontimezonecode: String? = null,
val versionnumber: Int? = null,
val _modifiedby_value: String? = null,
val _transactioncurrencyid_value: String? = null,
val entityimage: String? = null,
val entityimage_url: String? = null,
val createdon: String? = null,
val overriddencreatedon: String? = null,
val territoryid: String? = null,
val _organizationid_value: String? = null,
val name: String?
) : Serializable

Просмотреть файл

@ -0,0 +1,13 @@
package com.ttpsc.dynamics365fieldService.bll.models
import java.io.Serializable
class UserInfo(
val resourceId: String,
val email: String,
val fullName: String?,
val firstName: String?,
val lastName: String?
) : Serializable {
override fun toString() = "$resourceId | $email"
}

Просмотреть файл

@ -0,0 +1,28 @@
package com.ttpsc.dynamics365fieldService.bll.models
import android.os.Parcel
import android.os.Parcelable
import java.io.Serializable
class WorkOrder(
var id: String?,
var name: String? = null,
var summary: String? = null,
var description: String? = null,
var address1: String? = null,
var address2: String? = null,
var address3: String? = null,
var city: String? = null,
var stateOrProvince: String? = null,
var postalCode: String? = null,
var country: String? = null,
var serviceTerritoryId: String? = null,
var iotAlertId: String? = null,
var iotAlerts: MutableList<Alert> = mutableListOf(),
var notes: List<Note> = listOf(),
var videoAttachmentUrl: String? = null,
var imageAttachmentUrl: String? = null,
var documentAttachmentUrl: String? = null,
var serviceTerritory: ServiceTerritory? = null,
var iconicsLink: String? = null
) : Serializable

Просмотреть файл

@ -0,0 +1,40 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.google.gson.Gson
import com.ttpsc.dynamics365fieldService.AppConfiguration
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.ChangeBookableResourceBookingStatusOperation
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.models.dynamics.DalChangeStatusError
import com.ttpsc.dynamics365fieldService.dal.models.dynamics.DalChangeStatusErrorBody
import io.reactivex.rxjava3.core.Observable
import retrofit2.Response
import javax.inject.Inject
class DynamicsChangeBookableResourceBookingStatus @Inject constructor(
repository: DynamicsApiRepository
) :
ChangeBookableResourceBookingStatusOperation {
private val _repository = repository
override lateinit var bookingStatusId: String
override lateinit var bookableResourceBookingId: String
override fun execute(): Observable<Pair<Boolean, String?>> {
return _repository.changeBookableResourceBookingStatus(
bookableResourceBookingId,
mapOf("@odata.id" to "${AppConfiguration.endpoint}${AppConfiguration.ENDPOINT_SUFFIX}bookingstatuses($bookingStatusId)")
).executeOnBackground().map { response ->
if (response.isSuccessful == false) {
val error = Gson().fromJson(
response.errorBody()?.string(),
DalChangeStatusErrorBody::class.java
)
return@map Pair<Boolean, String?>(true, error.error?.message)
}
return@map Pair<Boolean, String?>(false, null)
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.google.gson.Gson
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.CreateNoteOperation
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.models.dynamics.DalChangeStatusErrorBody
import com.ttpsc.dynamics365fieldService.dal.models.dynamics.DalCreateNoteRequestBody
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import retrofit2.Response
import java.util.*
import javax.inject.Inject
class DynamicsCreateNoteOperation @Inject constructor(
repository: DynamicsApiRepository
) :
CreateNoteOperation {
override var filename: String? = null
override var subject: String? = null
override var isDocument: Boolean = false
override lateinit var objecttypecode: String
override var workOrderId: String? = null
override var documentBody: Array<Byte>? = null
private val _repository = repository
override fun execute(): Observable<Pair<Boolean, String?>> {
val note = DalCreateNoteRequestBody()
if (filename != null) {
note.filename = this.filename!!
}
if (subject != null) {
note.subject = this.subject!!
}
note.isdocument = this.isDocument
if (workOrderId != null) {
note.objecttypecode = "msdyn_workorder"
note.workOrderBindPath = "/msdyn_workorders($workOrderId)"
}
if(documentBody != null) {
note.base64documentbody = Base64.getEncoder().encodeToString(documentBody!!.toByteArray())
}
return _repository.createNote(note)
.executeOnBackground()
.map {response ->
if (response.isSuccessful == false) {
val error = Gson().fromJson(
response.errorBody()?.string(),
DalChangeStatusErrorBody::class.java
)
return@map Pair<Boolean, String?>(true, error.error?.message)
}
return@map Pair<Boolean, String?>(false, null)
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.DownloadFileOperation
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsDownloadFile @Inject constructor(dynamicsRepository: DynamicsApiRepository) :
DownloadFileOperation {
private val _dynamicsRepository = dynamicsRepository
override lateinit var fileUrl: String
override fun execute(): Observable<ByteArray> {
return _dynamicsRepository
.downloadFile(fileUrl)
.executeOnBackground()
.map { it?.bytes() }
}
}

Просмотреть файл

@ -0,0 +1,30 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetAlertsOperation
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetProceduresOperation
import com.ttpsc.dynamics365fieldService.bll.models.Alert
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
import com.ttpsc.dynamics365fieldService.core.abstraction.AuthorizationManager
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetAlerts @Inject constructor(
repository: DynamicsApiRepository
) :
GetAlertsOperation {
private val _repository = repository
override var queryMap: Map<String, String>? = null
override fun execute(): Observable<List<Alert>> {
return _repository.getAlerts(queryMap)
.executeOnBackground()
.map { it ->
it.value.map { dalAlert -> dalAlert.toAlert() }
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetBookableResourcesOperation
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetProceduresOperation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
import com.ttpsc.dynamics365fieldService.core.abstraction.AuthorizationManager
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetBookableResource @Inject constructor(
repository: DynamicsApiRepository,
authorizationManager: AuthorizationManager
) :
GetBookableResourcesOperation {
private val _repository = repository
private val _authorizationManager = authorizationManager
override fun execute(): Observable<BookableResource> {
val queryMap =
mapOf("\$filter" to "_userid_value eq ${_authorizationManager.getUserResourceId()}")
return _repository.getBookableResources(queryMap)
.executeOnBackground()
.map { it ->
it.value.first().toBookableResource()
//TODO remove and throw exception if there is no resource
/*if(it.value.count() > 0) {
it.value.first().toBookableResource()
}else{
BookableResource("", "6ff1d0b1-32a0-ea11-a812-000d3a33fb78")
}*/
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetBookableResourceBookingDetailsOperation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResourceBooking
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetBookableResourceBookingDetails @Inject constructor(
repository: DynamicsApiRepository
): GetBookableResourceBookingDetailsOperation {
private val _repository = repository
override var bookableResourceBookingId: String = ""
override fun execute(): Observable<BookableResourceBooking> {
val queryMap =
mutableMapOf("\$filter" to "bookableresourcebookingid eq $bookableResourceBookingId")
return _repository.getBookableResourceBookingDetails(queryMap)
.executeOnBackground()
.map { it ->
it.value.first().toBookableResourceBooking()
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetBookableResourceBookingsCountOperation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetBookableResourceBookingsCount @Inject constructor(
repository: DynamicsApiRepository
) :
GetBookableResourceBookingsCountOperation {
private val _repository = repository
override lateinit var bookableResource: BookableResource
override var query: Map<String, String>? = null
override fun execute(): Observable<Int> {
val queryMap =
mutableMapOf(
"\$filter" to "_resource_value eq ${bookableResource.bookableResourceId}",
"\$select" to "bookableresourcebookingid",
"\$count" to "true"
)
query?.let {
for (map in it){
if(queryMap[map.key] != null) {
queryMap[map.key] += " and " + map.value
}else {
queryMap[map.key] = map.value
}
}
}
return _repository.getBookableResourceBookings(queryMap, null)
.executeOnBackground()
.map { it ->
it.count
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetBookableResourcesBookingsOperation
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
import com.ttpsc.dynamics365fieldService.bll.models.BookableResourceBooking
import com.ttpsc.dynamics365fieldService.bll.models.PageableModel
import com.ttpsc.dynamics365fieldService.core.abstraction.AuthorizationManager
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import javax.inject.Inject
class DynamicsGetBookableResourceBookingsOperation @Inject constructor(
repository: DynamicsApiRepository) :
GetBookableResourcesBookingsOperation {
private val _repository = repository
override lateinit var bookableResource: BookableResource
override var query: Map<String, String>? = null
override var itemsPerPage: Int? = null
override fun execute(): Observable<PageableModel<List<BookableResourceBooking>>> {
val queryMap =
mutableMapOf("\$filter" to "_resource_value eq ${bookableResource.bookableResourceId}")
query?.let {
for (map in it){
if(queryMap[map.key] != null) {
queryMap[map.key] += " and " + map.value
}else {
queryMap[map.key] = map.value
}
}
}
var header: String? = null
if (itemsPerPage != null) {
header = "odata.maxpagesize=$itemsPerPage"
}
return _repository.getBookableResourceBookings(queryMap, header)
.executeOnBackground()
.map {
PageableModel(
it.value.map { dalBookableResourceBooking -> dalBookableResourceBooking.toBookableResourceBooking() },
it.nextPageLink
)
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetBookingStatusesOperation
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetProceduresOperation
import com.ttpsc.dynamics365fieldService.bll.models.BookingStatus
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetBookingStatusesOperation @Inject constructor(
repository: DynamicsApiRepository
) :
GetBookingStatusesOperation {
private val _repository = repository
override fun execute(): Observable<List<BookingStatus>> =
_repository.getBookingStatues()
.executeOnBackground()
.map { it ->
it.value.map { dalBookingStatus -> dalBookingStatus.toBookingStatus() }
}
}

Просмотреть файл

@ -0,0 +1,27 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetFormOperation
import com.ttpsc.dynamics365fieldService.bll.models.CustomField
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetForm @Inject constructor(
repository: DynamicsApiRepository
) : GetFormOperation {
private val _repository = repository
override var query: Map<String, String>? = null
override fun execute(): Observable<List<CustomField>> {
return _repository.downloadForm(query)
.executeOnBackground()
.map { it ->
if (it.value.count() > 0) {
it.value.first().parseForm().map { it.toCustomField() }
} else {
mutableListOf()
}
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetNotesOperation
import com.ttpsc.dynamics365fieldService.bll.models.Note
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetNotes @Inject constructor(
repository: DynamicsApiRepository
) :
GetNotesOperation {
private val _repository = repository
override var queryMap: Map<String, String>? = null
override fun execute(): Observable<List<Note>> =
_repository.getNotes(queryMap)
.executeOnBackground()
.map { it ->
it.value.map { dalNote -> dalNote.toNote() }
}.map {
it.sortedByDescending { note -> note.date }
}
}

Просмотреть файл

@ -0,0 +1,59 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.google.gson.Gson
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetRawWorkOrdersOperation
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.models.dynamics.DalChangeStatusErrorBody
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetRawWorkOrder @Inject
constructor(
repository: DynamicsApiRepository
) :
GetRawWorkOrdersOperation {
private val _repository = repository
override var queryMap: Map<String, String>? = null
override fun execute(): Observable<Pair<Map<String, String>, String?>> =
_repository.getRawWorkOrders(queryMap)
.executeOnBackground()
.map {responseBody ->
val bodyString = responseBody.body()
val fieldValueMap: MutableMap<String, String> = mutableMapOf()
if(responseBody.isSuccessful && bodyString != null) {
val responseString = bodyString.substringAfter("value\":")
.replace("{", "")
.replace("}", "")
.replace("]", "")
.replace("[", "")
.replace("\"", "")
val fieldValues = responseString.split(",")
for (fieldValue in fieldValues) {
val splitted = fieldValue.split(
delimiters = *arrayOf(":"),
ignoreCase = true,
limit = 2
)
if (splitted.count() > 1 && splitted[1].contains("null") == false) {
fieldValueMap.put(splitted[0], splitted[1])
}
}
}else
{
if (responseBody.isSuccessful == false) {
val error = Gson().fromJson(
responseBody.errorBody()?.string(),
DalChangeStatusErrorBody::class.java
)
return@map Pair<Map<String, String>, String?>(fieldValueMap, error.error?.message)
}
}
return@map Pair<Map<String, String>, String?>(fieldValueMap, null)
}
}

Просмотреть файл

@ -0,0 +1,32 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetUserInfoOperation
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetWorkOrdersOperation
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
import com.ttpsc.dynamics365fieldService.bll.models.UserInfo
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetUserInfo @Inject constructor(
repository: DynamicsApiRepository
) :
GetUserInfoOperation {
override lateinit var email: String
private val _repository = repository
override fun execute(): Observable<List<UserInfo>> {
val query = mapOf<String, String>(
"\$select" to "systemuserid, fullname, windowsliveid, domainname, firstname, lastname",
"\$filter" to "domainname eq $email"
)
return _repository.getUserInfo(query)
.executeOnBackground()
.map { it ->
it.value.map { dalUserInfo -> dalUserInfo.toUserInfo() }
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetBookingStatusesOperation
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.GetWorkOrdersOperation
import com.ttpsc.dynamics365fieldService.bll.models.BookingStatus
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
import com.ttpsc.dynamics365fieldService.bll.models.WorkOrder
import com.ttpsc.dynamics365fieldService.core.extensions.executeOnBackground
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsGetWorkOrdersOperation @Inject constructor(
repository: DynamicsApiRepository
) :
GetWorkOrdersOperation {
private val _repository = repository
override var queryMap: Map<String, String>? = null
override fun execute(): Observable<List<WorkOrder>> =
_repository.getWorkOrders(queryMap)
.executeOnBackground()
.map { it ->
it.value.map { dalWorkOrder -> dalWorkOrder.toWorkOrder() }
}
}

Просмотреть файл

@ -0,0 +1,63 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import android.app.Activity
import android.content.Context
import com.microsoft.identity.client.*
import com.microsoft.identity.client.exception.MsalException
import com.ttpsc.dynamics365fieldService.AndroidApplication
import com.ttpsc.dynamics365fieldService.R
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.LogInOperation
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.SaveEnvironmentDataOperation
import com.ttpsc.dynamics365fieldService.core.abstraction.AuthorizationManager
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlin.math.log
class DynamicsLogIn @Inject constructor(
private val authorizationManager: AuthorizationManager,
private val clientApplication: ISingleAccountPublicClientApplication,
private val saveEnvironmentDataOperation: SaveEnvironmentDataOperation
) : LogInOperation {
private val subject: PublishSubject<Boolean> = PublishSubject.create()
override var userName: String? = null
override var environmentUrl: String? = null
override var activity: Activity? = null
override fun execute(): Observable<Boolean> {
if (activity == null) {
return Observable.empty()
}
val login = if (userName != null) userName!! else ""
val environment = if (environmentUrl != null) environmentUrl!! else ""
clientApplication.signIn(activity!!, login, arrayOf("$environment//user_impersonation"), getAuthInteractiveCallback(userName, environmentUrl))
return subject
}
private fun getAuthInteractiveCallback(email:String?, environment: String?): AuthenticationCallback {
return object : AuthenticationCallback {
override fun onSuccess(authenticationResult: IAuthenticationResult) {
println("Successfully authenticated")
saveEnvironmentDataOperation.apply {
environmentUrl = environment
userEmail = email
}.execute().subscribe()
authorizationManager.setAuthorizationToken(authenticationResult.accessToken)
authorizationManager.saveTokenExpirationDate(authenticationResult.expiresOn)
subject.onNext(true)
}
override fun onError(exception: MsalException) {
println("Authentication failed: $exception")
subject.onError(exception)
}
override fun onCancel() {
subject.onNext(false)
}
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
package com.ttpsc.dynamics365fieldService.bll.operations.dynamics
import android.content.SharedPreferences
import com.ttpsc.dynamics365fieldService.AppConfiguration
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.SaveEnvironmentDataOperation
import com.ttpsc.dynamics365fieldService.core.managers.DynamicsAuthorizationManager
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
class DynamicsSaveEnvironmentDataOperation @Inject constructor(val sharedPreferences: SharedPreferences) :
SaveEnvironmentDataOperation {
override var environmentUrl: String? = null
override var userEmail: String? = null
override fun execute(): Observable<Unit> {
return Observable.create { observer ->
if (environmentUrl == null || userEmail == null) {
observer.onNext(Unit)
}
sharedPreferences
.edit()
.putString(AppConfiguration.StorageKeys.environmentKey, environmentUrl)
.putString(AppConfiguration.StorageKeys.userAccountKey, userEmail)
.apply()
AppConfiguration.endpoint = environmentUrl!!
observer.onNext(Unit)
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
package com.ttpsc.dynamics365fieldService.core.abstraction
import com.ttpsc.dynamics365fieldService.bll.models.UserInfo
import io.reactivex.rxjava3.core.Observable
import okhttp3.Authenticator
import okhttp3.Interceptor
import java.util.*
interface AuthorizationManager: Interceptor, Authenticator {
fun setAuthorizationToken(authToken: String)
fun getAuthorizationHeader(): String?
fun signOut()
fun setUserData(userInfo: UserInfo?)
fun getUserEmail(): String?
fun getUserResourceId(): String?
val userNeedsLogin: Observable<Unit>
fun saveTokenExpirationDate(date: Date)
fun getUserFirstName(): String?
fun getUserLastName(): String?
}

Просмотреть файл

@ -0,0 +1,10 @@
package com.ttpsc.dynamics365fieldService.core.abstraction
import android.app.Activity
import io.reactivex.rxjava3.core.Observable
interface LanguageManager {
val languageChanged: Observable<String>
fun getLanguage(): String
fun registerForLanguageChange(activity: Activity)
}

Просмотреть файл

@ -0,0 +1,12 @@
package com.ttpsc.dynamics365fieldService.core.abstraction
import io.reactivex.rxjava3.core.Observable
interface LifecycleProvidingActivity {
val onCreate: Observable<Unit>
val onStart: Observable<Unit>
val onResume: Observable<Unit>
val onPause: Observable<Unit>
val onStop: Observable<Unit>
val onDestroy: Observable<Unit>
}

Просмотреть файл

@ -0,0 +1,28 @@
package com.ttpsc.dynamics365fieldService.core.di
import com.ttpsc.dynamics365fieldService.AndroidApplication
import com.ttpsc.dynamics365fieldService.core.di.api.AuthenticationModule
import com.ttpsc.dynamics365fieldService.core.di.api.DynamicsApiModule
import com.ttpsc.dynamics365fieldService.core.di.viewmodel.ViewModelModule
import com.ttpsc.dynamics365fieldService.views.*
import com.ttpsc.dynamics365fieldService.views.activities.MainActivity
import dagger.Component
import javax.inject.Singleton
@Singleton
@Component(modules = [ApplicationModule::class, DynamicsApiModule::class, ViewModelModule::class, AuthenticationModule::class])
interface ApplicationComponent {
fun inject(application: AndroidApplication)
fun inject(loginFragment: LoginFragment)
fun inject(guidesListFragment: GuidesListFragment)
fun inject(guideSummaryFragment: GuideSummaryFragment)
fun inject(guideDetailsFragment: GuideDetailsFragment)
fun inject(startFragment: StartFragment)
fun inject(mainActivity: MainActivity)
fun inject(notesListFragment: NotesListFragment)
fun inject(guideDetailsAttachmentsFragment: GuideDetailsAttachmentsFragment)
fun inject(mainMenuFragment: MainMenuFragment)
fun inject(noteDetailsFragment: NoteDetailsFragment)
}

Просмотреть файл

@ -0,0 +1,133 @@
package com.ttpsc.dynamics365fieldService.core.di
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.ContextCompat.startActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.google.gson.GsonBuilder
import com.microsoft.identity.client.*
import com.ttpsc.dynamics365fieldService.AndroidApplication
import com.ttpsc.dynamics365fieldService.AppConfiguration
import com.ttpsc.dynamics365fieldService.BuildConfig
import com.ttpsc.dynamics365fieldService.R
import com.ttpsc.dynamics365fieldService.core.abstraction.AuthorizationManager
import com.ttpsc.dynamics365fieldService.core.abstraction.LanguageManager
import com.ttpsc.dynamics365fieldService.core.managers.ApiLogger
import com.ttpsc.dynamics365fieldService.core.managers.SystemLanguageManager
import dagger.Module
import dagger.Provides
import hu.akarnokd.rxjava3.retrofit.RxJava3CallAdapterFactory
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.*
import okhttp3.*
import okhttp3.internal.immutableListOf
import okhttp3.logging.HttpLoggingInterceptor
import org.reactivestreams.Subscriber
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
@Module
class ApplicationModule(private val application: AndroidApplication) {
@Provides
@Singleton
fun provideApplicationContext(): Context = application
@Provides
@Singleton
fun providesLanguageManager(): LanguageManager = SystemLanguageManager()
@Inject
@Provides
@Singleton
fun provideRetrofit(
@Named("BASE_ENDPOINT") baseEndpoint: String,
authorizationManager: AuthorizationManager
): Retrofit {
return Retrofit.Builder()
.baseUrl(baseEndpoint)
.client(createClient(authorizationManager))
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(createGsonConverter()))
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
}
@Provides
@Singleton
fun providePublicClientApplication(context: Context): ISingleAccountPublicClientApplication {
return HackySingleAccountPublicClientApplication(
PublicClientApplicationConfigurationFactory.initializeConfiguration(
context.applicationContext,
R.raw.auth_config_maksym
)
)
}
class HackySingleAccountPublicClientApplication(c: PublicClientApplicationConfiguration) :
SingleAccountPublicClientApplication(c)
@Provides
@Singleton
fun provideSharedPreferences(): SharedPreferences {
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
return EncryptedSharedPreferences
.create(
"AppPreferences",
masterKeyAlias,
application,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
class EndpointInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val desiredEndpoint = AppConfiguration.endpoint + AppConfiguration.ENDPOINT_SUFFIX
val tag = chain.request().tag(String::class.java)
if (tag.isNullOrEmpty() || tag != AppConfiguration.RequestTags.leaveUrl) {
val currentUrl = chain.request().url.toString()
if (currentUrl.startsWith(desiredEndpoint))
return chain.proceed(chain.request())
val urlPath = currentUrl.substring(
currentUrl.indexOf(AppConfiguration.ENDPOINT_SUFFIX)
+ AppConfiguration.ENDPOINT_SUFFIX.length
)
val newRequest = chain.request().newBuilder()
.url(desiredEndpoint + urlPath)
.build()
return chain.proceed(newRequest)
} else {
return chain.proceed(chain.request())
}
}
}
private fun createClient(authorizationManager: AuthorizationManager): OkHttpClient {
val okHttpClientBuilder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val loggingInterceptor =
HttpLoggingInterceptor(ApiLogger()).setLevel(HttpLoggingInterceptor.Level.BODY)
okHttpClientBuilder.addInterceptor(loggingInterceptor)
}
okHttpClientBuilder.addInterceptor(EndpointInterceptor())
okHttpClientBuilder.addInterceptor(authorizationManager)
okHttpClientBuilder.authenticator(authorizationManager)
return okHttpClientBuilder.build()
}
private fun createGsonConverter() =
GsonBuilder()
.create()
}

Просмотреть файл

@ -0,0 +1,27 @@
package com.ttpsc.dynamics365fieldService.core.di.api
import android.content.SharedPreferences
import com.microsoft.identity.client.ISingleAccountPublicClientApplication
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.LogInOperation
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.SaveEnvironmentDataOperation
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.UpdateUserSessionOperation
import com.ttpsc.dynamics365fieldService.bll.operations.dynamics.DynamicsLogIn
import com.ttpsc.dynamics365fieldService.bll.operations.dynamics.DynamicsSaveEnvironmentDataOperation
import com.ttpsc.dynamics365fieldService.core.abstraction.AuthorizationManager
import dagger.Module
import dagger.Provides
@Module
class AuthenticationModule {
@Provides
fun provideLogInOperation(
authorizationManager: AuthorizationManager,
clientApplication: ISingleAccountPublicClientApplication,
saveEnvironmentOperation: SaveEnvironmentDataOperation
): LogInOperation = DynamicsLogIn(authorizationManager, clientApplication, saveEnvironmentOperation)
@Provides
fun provideSaveEnvironmentOperation(sharedPreferences: SharedPreferences): SaveEnvironmentDataOperation =
DynamicsSaveEnvironmentDataOperation(sharedPreferences)
}

Просмотреть файл

@ -0,0 +1,132 @@
package com.ttpsc.dynamics365fieldService.core.di.api
import android.content.SharedPreferences
import com.microsoft.identity.client.ISingleAccountPublicClientApplication
import com.ttpsc.dynamics365fieldService.AppConfiguration
import com.ttpsc.dynamics365fieldService.bll.abstraction.operations.*
import com.ttpsc.dynamics365fieldService.bll.operations.dynamics.*
import com.ttpsc.dynamics365fieldService.core.abstraction.AuthorizationManager
import com.ttpsc.dynamics365fieldService.core.abstraction.LanguageManager
import com.ttpsc.dynamics365fieldService.core.managers.DynamicsAuthorizationManager
import com.ttpsc.dynamics365fieldService.dal.repository.DynamicsApiRepository
import dagger.Module
import dagger.Provides
import retrofit2.Retrofit
import javax.inject.Named
import javax.inject.Singleton
@Module
class DynamicsApiModule {
@Provides
@Named("BASE_ENDPOINT")
fun getBaseEndpoint(): String {
return AppConfiguration.endpoint + AppConfiguration.ENDPOINT_SUFFIX
}
@Provides
@Singleton
fun provideAuthorizationManager(
sharedPreferences: SharedPreferences,
publicClientApplication: ISingleAccountPublicClientApplication
): AuthorizationManager =
DynamicsAuthorizationManager(sharedPreferences, publicClientApplication)
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit) =
DynamicsApiRepository(retrofit)
@Provides
fun providesGetUserInfoOperation(
repository: DynamicsApiRepository
): GetUserInfoOperation = DynamicsGetUserInfo(repository)
@Provides
fun provideGetProcedureDetailsOperation(
repository: DynamicsApiRepository
): GetBookableResourceBookingDetailsOperation =
DynamicsGetBookableResourceBookingDetails(repository)
@Provides
fun provideGetProceduresOperation(
repository: DynamicsApiRepository,
languageManager: LanguageManager
): GetBookableResourcesBookingsOperation =
DynamicsGetBookableResourceBookingsOperation(repository)
@Provides
fun provideGetBookableResourceBookingsOperationCount(
repository: DynamicsApiRepository
): GetBookableResourceBookingsCountOperation =
DynamicsGetBookableResourceBookingsCount(repository)
@Provides
fun provideGetAlertsOperation(
repository: DynamicsApiRepository
): GetAlertsOperation = DynamicsGetAlerts(repository)
@Provides
fun providesGetBookingStatusesOperation(
repository: DynamicsApiRepository
): GetBookingStatusesOperation = DynamicsGetBookingStatusesOperation(repository)
@Provides
fun provideWorkOrdersOperation(repository: DynamicsApiRepository): GetWorkOrdersOperation =
DynamicsGetWorkOrdersOperation(repository)
@Provides
fun provideGetBookableResourcesOperation(
repository: DynamicsApiRepository,
authorizationManager: AuthorizationManager
): GetBookableResourcesOperation =
DynamicsGetBookableResource(repository, authorizationManager)
@Provides
fun providesChangeBookableResourceBookingStatus(
repository: DynamicsApiRepository
): ChangeBookableResourceBookingStatusOperation =
DynamicsChangeBookableResourceBookingStatus(repository)
@Provides
fun provideGetNotesOperation(
repository: DynamicsApiRepository
): GetNotesOperation = DynamicsGetNotes(repository)
@Provides
fun provideCreateNoteOperation(
repository: DynamicsApiRepository
): CreateNoteOperation = DynamicsCreateNoteOperation(repository)
@Provides
fun providesDownloadFileOperation(repository: DynamicsApiRepository): DownloadFileOperation =
DynamicsDownloadFile(repository)
@Provides
fun providesGetFormOperation(repository: DynamicsApiRepository): GetFormOperation =
DynamicsGetForm(repository)
@Provides
fun providesGetRawWorkOrdersOperation(repository: DynamicsApiRepository): GetRawWorkOrdersOperation =
DynamicsGetRawWorkOrder(repository)
@Provides
fun provideGetCategoriesOperation(repository: DynamicsApiRepository): GetCategoriesOperation {
throw NotImplementedError()
}
@Provides
fun provideGetGuideDetailsOperation(repository: DynamicsApiRepository): GetProcedureOperation {
throw NotImplementedError()
}
@Provides
fun providesCreateProcedureOperation(repository: DynamicsApiRepository): CreateProcedureOperation {
throw NotImplementedError()
}
@Provides
fun providesStepProcedureOperation(repository: DynamicsApiRepository): CreateStepOperation {
throw NotImplementedError()
}
}

Просмотреть файл

@ -0,0 +1,28 @@
package com.ttpsc.dynamics365fieldService.core.di.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
@Singleton
@Suppress("UNCHECKED_CAST")
class ViewModelFactory
@Inject constructor(
private val creators: Map<Class<out ViewModel>,
@JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.asIterable()
.firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("Unknown ViewModel class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
package com.ttpsc.dynamics365fieldService.core.di.viewmodel
import androidx.lifecycle.ViewModel
import dagger.MapKey
import kotlin.reflect.KClass
@MapKey
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)

Просмотреть файл

@ -0,0 +1,65 @@
package com.ttpsc.dynamics365fieldService.core.di.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.ttpsc.dynamics365fieldService.viewmodels.*
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
@Suppress("unused")
@Module
abstract class ViewModelModule {
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@ViewModelKey(LoginViewModel::class)
abstract fun bindsLoginViewModel(viewModel: LoginViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(StartViewModel::class)
abstract fun bindsStartViewModel(viewModel: StartViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ProcedureDetailsViewModel::class)
abstract fun bindsProcedureDetailsViewModel(viewModel: ProcedureDetailsViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ProcedureStepViewModel::class)
abstract fun bindsProcedureStepViewModel(viewModel: ProcedureStepViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ProcedureStepsViewModel::class)
abstract fun bindsProcedureStepsViewModel(viewModel: ProcedureStepsViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(GalleryViewModel::class)
abstract fun bindsGalleryViewModel(viewModel: GalleryViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(SettingsViewModel::class)
abstract fun bindsSettingsViewModel(viewModel: SettingsViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ProcedureListViewModel::class)
abstract fun bindsProcedureListViewModel(viewModel: ProcedureListViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(MainViewModel::class)
abstract fun bindsMainViewModel(viewModel: MainViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(NoteDetailsViewModel::class)
abstract fun bindsNoteDetailsViewModel(viewModel: NoteDetailsViewModel): ViewModel
}

Просмотреть файл

@ -0,0 +1,12 @@
package com.ttpsc.dynamics365fieldService.core.extensions
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.annotations.NonNull
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
fun <T> Observable<@NonNull T>.executeOnBackground(): @NonNull Observable<@NonNull T> {
return this.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
}

Просмотреть файл

@ -0,0 +1,78 @@
package com.ttpsc.dynamics365fieldService.core.extensions
import com.google.gson.annotations.SerializedName
import java.lang.reflect.Field
@Suppress("SpellCheckingInspection")
private const val BASE64_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@Suppress("SpellCheckingInspection")
private val RX_BASE64_CLEANR = "[^=" + BASE64_SET + "]".toRegex()
@Suppress("unused", "unused")
val String.base64encoded: String
get() {
val pad = when (this.length % 3) {
1 -> "=="
2 -> "="
else -> ""
}
val raw = this + 0.toChar().toString().repeat(minOf(0, pad.lastIndex))
return raw.chunkedSequence(3) {
Triple(
it[0].toInt(),
it[1].toInt(),
it[2].toInt()
)
}.map { (first, second, third) ->
(0xFF.and(first) shl 16) +
(0xFF.and(second) shl 8) +
0xFF.and(third)
}.map { n ->
sequenceOf(
(n shr 18) and 0x3F,
(n shr 12) and 0x3F,
(n shr 6) and 0x3F,
n and 0x3F
)
}.flatten()
.map { BASE64_SET[it] }
.joinToString("")
.dropLast(pad.length) + pad
}
@Suppress("unused")
val String.base64decoded: String
get() {
require(this.length % 4 != 0) { "The string \"$this\" does not comply with BASE64 length requirement." }
val clean = this.replace(RX_BASE64_CLEANR, "").replace("=", "A")
val padLen: Int = this.count { it == '=' }
return clean.chunkedSequence(4) {
(BASE64_SET.indexOf(clean[0]) shl 18) +
(BASE64_SET.indexOf(clean[1]) shl 12) +
(BASE64_SET.indexOf(clean[2]) shl 6) +
BASE64_SET.indexOf(clean[3])
}.map { n ->
sequenceOf(
0xFF.and(n shr 16),
0xFF.and(n shr 8),
0xFF.and(n)
)
}.flatten()
.map { it.toChar() }
.joinToString("")
.dropLast(padLen)
}
fun Enum<*>.getSerializedName(): String? {
return try {
val f: Field = this.javaClass.getField(this.name)
val a: SerializedName = f.getAnnotation(SerializedName::class.java)
a.value
} catch (ignored: NoSuchFieldException) {
null
}
}

Просмотреть файл

@ -0,0 +1,25 @@
package com.ttpsc.dynamics365fieldService.core.managers
import android.util.Log
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.JsonSyntaxException
import okhttp3.logging.HttpLoggingInterceptor
class ApiLogger : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
val logName = "ApiLogger"
if (message.startsWith("{") || message.startsWith("[")) {
try {
val prettyPrintJson = GsonBuilder().setPrettyPrinting()
.create().toJson(JsonParser().parse(message))
Log.d(logName, prettyPrintJson)
} catch (m: JsonSyntaxException) {
Log.e(logName, message, m)
}
} else {
Log.d(logName, message)
return
}
}
}

Просмотреть файл

@ -0,0 +1,202 @@
package com.ttpsc.dynamics365fieldService.core.managers
import android.content.SharedPreferences
import android.util.Log
import android.webkit.CookieManager
import android.webkit.CookieSyncManager
import com.google.gson.Gson
import com.microsoft.identity.client.ISingleAccountPublicClientApplication
import com.ttpsc.dynamics365fieldService.AppConfiguration
import com.ttpsc.dynamics365fieldService.bll.models.UserInfo
import com.ttpsc.dynamics365fieldService.core.abstraction.AuthorizationManager
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import java.io.IOException
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.chrono.ChronoLocalDateTime
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class DynamicsAuthorizationManager @Inject constructor(
private val sharedPreferences: SharedPreferences,
private val clientApplication: ISingleAccountPublicClientApplication
) :
AuthorizationManager {
private var _token: String? = null
private var _expirationDate: Date? = null
private var _userResourceId: String? = null
private var _userFirstName: String? = null
private var _userLastName: String? = null
private var _environment: String? = null
private var userNeedsLoginSubject: PublishSubject<Unit> = PublishSubject.create()
override var userNeedsLogin: Observable<Unit> =
userNeedsLoginSubject.throttleFirst(30, TimeUnit.SECONDS)
init {
_token = sharedPreferences.getString(AppConfiguration.StorageKeys.accessTokenKey, null)
_userResourceId =
sharedPreferences.getString(AppConfiguration.StorageKeys.userResourceIdKey, null)
_userFirstName =
sharedPreferences.getString(AppConfiguration.StorageKeys.userFirstNameKey, null)
_userLastName =
sharedPreferences.getString(AppConfiguration.StorageKeys.userLastNameKey, null)
val serializedDate = sharedPreferences.getString(
AppConfiguration.StorageKeys.accessTokenExpirationDateKey,
null
)
if (serializedDate != null) {
_expirationDate = Gson().fromJson(serializedDate, Date::class.java)
}
_environment = sharedPreferences.getString(
AppConfiguration.StorageKeys.environmentKey,
null
)
if (_token == null || _environment == null) {
try {
signOut()
} catch (ex: Exception) {
Log.w("AUTOMATIC SIGN OUT EXCEPTION", ex.message)
}
}
}
override fun setAuthorizationToken(authToken: String) {
_token = authToken
sharedPreferences
.edit()
.putString(AppConfiguration.StorageKeys.accessTokenKey, _token)
.apply()
}
override fun getAuthorizationHeader(): String? {
if (_token == null) {
return null
}
return "Bearer $_token"
}
override fun saveTokenExpirationDate(date: Date) {
_expirationDate = date
sharedPreferences
.edit()
.putString(
AppConfiguration.StorageKeys.accessTokenExpirationDateKey,
Gson().toJson(date)
)
.apply()
}
override fun signOut() {
_token = null
_userResourceId = null
AppConfiguration.endpoint = ""
sharedPreferences
.edit()
.remove(AppConfiguration.StorageKeys.accessTokenKey)
.remove(AppConfiguration.StorageKeys.environmentKey)
.remove(AppConfiguration.StorageKeys.userResourceIdKey)
.remove(AppConfiguration.StorageKeys.userFirstNameKey)
.remove(AppConfiguration.StorageKeys.userLastNameKey)
.apply()
GlobalScope.async {
clientApplication.signOut()
}
}
override fun setUserData(userInfo: UserInfo?) {
if (userInfo != null) {
_userResourceId = userInfo.resourceId
_userFirstName = userInfo.firstName
_userLastName = userInfo.lastName
sharedPreferences
.edit()
.putString(AppConfiguration.StorageKeys.userResourceIdKey, userInfo.resourceId)
.apply()
sharedPreferences
.edit()
.putString(AppConfiguration.StorageKeys.userFirstNameKey, userInfo.firstName)
.apply()
sharedPreferences
.edit()
.putString(AppConfiguration.StorageKeys.userLastNameKey, userInfo.lastName)
.apply()
}
}
override fun getUserEmail(): String? =
sharedPreferences.getString(AppConfiguration.StorageKeys.userAccountKey, null)
override fun getUserFirstName(): String? =
sharedPreferences.getString(AppConfiguration.StorageKeys.userFirstNameKey, null)
override fun getUserLastName(): String? =
sharedPreferences.getString(AppConfiguration.StorageKeys.userLastNameKey, null)
override fun getUserResourceId(): String? = _userResourceId
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
request = request.newBuilder()
.build()
return chain.proceed(request)
}
@Throws(IOException::class)
override fun authenticate(route: Route?, response: Response): Request? {
var requestAvailable: Request? = null
try {
if (_environment.isNullOrEmpty()) {
_environment =
sharedPreferences.getString(AppConfiguration.StorageKeys.environmentKey, null)
}
val now = LocalDateTime.now()
val expirationDate =
_expirationDate?.toInstant()?.atZone(ZoneId.systemDefault())?.toLocalDateTime()
if (_token == null || (_expirationDate != null && now.isAfter(expirationDate))) {
println("TRYING TO GET ANOTHER TOKEN")
val authority =
clientApplication.getConfiguration().getDefaultAuthority().getAuthorityURL()
.toString()
val result = clientApplication.acquireTokenSilent(
arrayOf("$_environment//user_impersonation"),
authority
)
saveTokenExpirationDate(result.expiresOn)
if (result.accessToken != _token) {
setAuthorizationToken(result.accessToken)
}
}
println("Access token: $_token")
requestAvailable = response.request.newBuilder().addHeader(
"Authorization",
getAuthorizationHeader()!!
).build()
return requestAvailable
} catch (ex: Exception) {
signOut()
userNeedsLoginSubject.onNext(Unit)
}
return requestAvailable
}
}

Просмотреть файл

@ -0,0 +1,68 @@
package com.ttpsc.dynamics365fieldService.core.managers
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.ttpsc.dynamics365fieldService.core.abstraction.LanguageManager
import com.ttpsc.dynamics365fieldService.core.abstraction.LifecycleProvidingActivity
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.subjects.BehaviorSubject
import java.util.*
class SystemLanguageManager : LanguageManager {
private val _languageChangeSubject: BehaviorSubject<String> = BehaviorSubject.create()
private var lifecycleActivity: LifecycleProvidingActivity? = null
private var _languageReceiverRegistered = false
override val languageChanged: Observable<String>
get() = _languageChangeSubject
override fun getLanguage(): String =
Locale.getDefault().language.toLowerCase(Locale.ROOT)
override fun registerForLanguageChange(activity: Activity) {
lifecycleActivity = activity as LifecycleProvidingActivity
val filter = IntentFilter(Intent.ACTION_LOCALE_CHANGED)
val languageReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
_languageChangeSubject.onNext(getLanguage())
}
}
lifecycleActivity?.onStop?.subscribe {
unregisterReceiver(activity, languageReceiver)
}
lifecycleActivity?.onDestroy?.subscribe {
unregisterReceiver(activity, languageReceiver)
}
lifecycleActivity?.onResume?.subscribe {
registerReceiver(activity, languageReceiver, filter)
}
}
private fun registerReceiver(
activity: Activity,
languageReceiver: BroadcastReceiver,
filter: IntentFilter
) {
if (_languageReceiverRegistered == false) {
activity.registerReceiver(languageReceiver, filter)
_languageReceiverRegistered = true
}
}
private fun unregisterReceiver(
activity: Activity,
languageReceiver: BroadcastReceiver
) {
if (_languageReceiverRegistered) {
activity.unregisterReceiver(languageReceiver)
_languageReceiverRegistered = false
}
}
}

Просмотреть файл

@ -0,0 +1,120 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
import android.annotation.TargetApi;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLExt;
import android.opengl.EGLSurface;
import android.os.Build;
import android.view.Surface;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class InputSurface {
private static final boolean VERBOSE = false;
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
private static final int EGL_OPENGL_ES2_BIT = 4;
private EGLDisplay mEGLDisplay;
private EGLContext mEGLContext;
private EGLSurface mEGLSurface;
private Surface mSurface;
public InputSurface(Surface surface) {
if (surface == null) {
throw new NullPointerException();
}
mSurface = surface;
eglSetup();
}
private void eglSetup() {
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
mEGLDisplay = null;
throw new RuntimeException("unable to initialize EGL14");
}
int[] attribList = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RECORDABLE_ANDROID, 1,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
numConfigs, 0)) {
throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
}
int[] attrib_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, attrib_list, 0);
checkEglError("eglCreateContext");
if (mEGLContext == null) {
throw new RuntimeException("null context");
}
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface,
surfaceAttribs, 0);
checkEglError("eglCreateWindowSurface");
if (mEGLSurface == null) {
throw new RuntimeException("surface was null");
}
}
public void release() {
if (EGL14.eglGetCurrentContext().equals(mEGLContext)) {
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
}
EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
mSurface.release();
mEGLDisplay = null;
mEGLContext = null;
mEGLSurface = null;
mSurface = null;
}
public void makeCurrent() {
if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
}
public boolean swapBuffers() {
return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
}
public Surface getSurface() {
return mSurface;
}
public void setPresentationTime(long nsecs) {
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
}
private void checkEglError(String msg) {
boolean failed = false;
int error;
while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
failed = true;
}
if (failed) {
throw new RuntimeException("EGL error encountered (see log)");
}
}
}

Просмотреть файл

@ -0,0 +1,436 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaFormat;
import com.coremedia.iso.BoxParser;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.IsoTypeWriter;
import com.coremedia.iso.boxes.Box;
import com.coremedia.iso.boxes.Container;
import com.coremedia.iso.boxes.DataEntryUrlBox;
import com.coremedia.iso.boxes.DataInformationBox;
import com.coremedia.iso.boxes.DataReferenceBox;
import com.coremedia.iso.boxes.FileTypeBox;
import com.coremedia.iso.boxes.HandlerBox;
import com.coremedia.iso.boxes.MediaBox;
import com.coremedia.iso.boxes.MediaHeaderBox;
import com.coremedia.iso.boxes.MediaInformationBox;
import com.coremedia.iso.boxes.MovieBox;
import com.coremedia.iso.boxes.MovieHeaderBox;
import com.coremedia.iso.boxes.SampleSizeBox;
import com.coremedia.iso.boxes.SampleTableBox;
import com.coremedia.iso.boxes.SampleToChunkBox;
import com.coremedia.iso.boxes.StaticChunkOffsetBox;
import com.coremedia.iso.boxes.SyncSampleBox;
import com.coremedia.iso.boxes.TimeToSampleBox;
import com.coremedia.iso.boxes.TrackBox;
import com.coremedia.iso.boxes.TrackHeaderBox;
import com.googlecode.mp4parser.DataSource;
import com.googlecode.mp4parser.util.Matrix;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@TargetApi(16)
public class MP4Builder {
private InterleaveChunkMdat mdat = null;
private Mp4Movie currentMp4Movie = null;
private FileOutputStream fos = null;
private FileChannel fc = null;
private long dataOffset = 0;
private long writedSinceLastMdat = 0;
private boolean writeNewMdat = true;
private HashMap<Track, long[]> track2SampleSizes = new HashMap<>();
private ByteBuffer sizeBuffer = null;
public MP4Builder createMovie(Mp4Movie mp4Movie) throws Exception {
currentMp4Movie = mp4Movie;
fos = new FileOutputStream(mp4Movie.getCacheFile());
fc = fos.getChannel();
FileTypeBox fileTypeBox = createFileTypeBox();
fileTypeBox.getBox(fc);
dataOffset += fileTypeBox.getSize();
writedSinceLastMdat += dataOffset;
mdat = new InterleaveChunkMdat();
sizeBuffer = ByteBuffer.allocateDirect(4);
return this;
}
private void flushCurrentMdat() throws Exception {
long oldPosition = fc.position();
fc.position(mdat.getOffset());
mdat.getBox(fc);
fc.position(oldPosition);
mdat.setDataOffset(0);
mdat.setContentSize(0);
fos.flush();
}
public boolean writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo, boolean isAudio) throws Exception {
if (writeNewMdat) {
mdat.setContentSize(0);
mdat.getBox(fc);
mdat.setDataOffset(dataOffset);
dataOffset += 16;
writedSinceLastMdat += 16;
writeNewMdat = false;
}
mdat.setContentSize(mdat.getContentSize() + bufferInfo.size);
writedSinceLastMdat += bufferInfo.size;
boolean flush = false;
if (writedSinceLastMdat >= 32 * 1024) {
flushCurrentMdat();
writeNewMdat = true;
flush = true;
writedSinceLastMdat -= 32 * 1024;
}
currentMp4Movie.addSample(trackIndex, dataOffset, bufferInfo);
byteBuf.position(bufferInfo.offset + (isAudio ? 0 : 4));
byteBuf.limit(bufferInfo.offset + bufferInfo.size);
if (!isAudio) {
sizeBuffer.position(0);
sizeBuffer.putInt(bufferInfo.size - 4);
sizeBuffer.position(0);
fc.write(sizeBuffer);
}
fc.write(byteBuf);
dataOffset += bufferInfo.size;
if (flush) {
fos.flush();
}
return flush;
}
public int addTrack(MediaFormat mediaFormat, boolean isAudio) throws Exception {
return currentMp4Movie.addTrack(mediaFormat, isAudio);
}
public void finishMovie(boolean error) throws Exception {
if (mdat.getContentSize() != 0) {
flushCurrentMdat();
}
for (Track track : currentMp4Movie.getTracks()) {
List<Sample> samples = track.getSamples();
long[] sizes = new long[samples.size()];
for (int i = 0; i < sizes.length; i++) {
sizes[i] = samples.get(i).getSize();
}
track2SampleSizes.put(track, sizes);
}
Box moov = createMovieBox(currentMp4Movie);
moov.getBox(fc);
fos.flush();
fc.close();
fos.close();
}
protected FileTypeBox createFileTypeBox() {
LinkedList<String> minorBrands = new LinkedList<>();
minorBrands.add("isom");
minorBrands.add("3gp4");
return new FileTypeBox("isom", 0, minorBrands);
}
private class InterleaveChunkMdat implements Box {
private Container parent;
private long contentSize = 1024 * 1024 * 1024;
private long dataOffset = 0;
public Container getParent() {
return parent;
}
public long getOffset() {
return dataOffset;
}
public void setDataOffset(long offset) {
dataOffset = offset;
}
public void setParent(Container parent) {
this.parent = parent;
}
public void setContentSize(long contentSize) {
this.contentSize = contentSize;
}
public long getContentSize() {
return contentSize;
}
public String getType() {
return "mdat";
}
public long getSize() {
return 16 + contentSize;
}
private boolean isSmallBox(long contentSize) {
return (contentSize + 8) < 4294967296L;
}
@Override
public void parse(DataSource dataSource, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {
}
public void getBox(WritableByteChannel writableByteChannel) throws IOException {
ByteBuffer bb = ByteBuffer.allocate(16);
long size = getSize();
if (isSmallBox(size)) {
IsoTypeWriter.writeUInt32(bb, size);
} else {
IsoTypeWriter.writeUInt32(bb, 1);
}
bb.put(IsoFile.fourCCtoBytes("mdat"));
if (isSmallBox(size)) {
bb.put(new byte[8]);
} else {
IsoTypeWriter.writeUInt64(bb, size);
}
bb.rewind();
writableByteChannel.write(bb);
}
}
public static long gcd(long a, long b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
public long getTimescale(Mp4Movie mp4Movie) {
long timescale = 0;
if (!mp4Movie.getTracks().isEmpty()) {
timescale = mp4Movie.getTracks().iterator().next().getTimeScale();
}
for (Track track : mp4Movie.getTracks()) {
timescale = gcd(track.getTimeScale(), timescale);
}
return timescale;
}
protected MovieBox createMovieBox(Mp4Movie movie) {
MovieBox movieBox = new MovieBox();
MovieHeaderBox mvhd = new MovieHeaderBox();
mvhd.setCreationTime(new Date());
mvhd.setModificationTime(new Date());
mvhd.setMatrix(Matrix.ROTATE_0);
long movieTimeScale = getTimescale(movie);
long duration = 0;
for (Track track : movie.getTracks()) {
long tracksDuration = track.getDuration() * movieTimeScale / track.getTimeScale();
if (tracksDuration > duration) {
duration = tracksDuration;
}
}
mvhd.setDuration(duration);
mvhd.setTimescale(movieTimeScale);
mvhd.setNextTrackId(movie.getTracks().size() + 1);
movieBox.addBox(mvhd);
for (Track track : movie.getTracks()) {
movieBox.addBox(createTrackBox(track, movie));
}
return movieBox;
}
protected TrackBox createTrackBox(Track track, Mp4Movie movie) {
TrackBox trackBox = new TrackBox();
TrackHeaderBox tkhd = new TrackHeaderBox();
tkhd.setEnabled(true);
tkhd.setInMovie(true);
tkhd.setInPreview(true);
if (track.isAudio()) {
tkhd.setMatrix(Matrix.ROTATE_0);
} else {
tkhd.setMatrix(movie.getMatrix());
}
tkhd.setAlternateGroup(0);
tkhd.setCreationTime(track.getCreationTime());
tkhd.setDuration(track.getDuration() * getTimescale(movie) / track.getTimeScale());
tkhd.setHeight(track.getHeight());
tkhd.setWidth(track.getWidth());
tkhd.setLayer(0);
tkhd.setModificationTime(new Date());
tkhd.setTrackId(track.getTrackId() + 1);
tkhd.setVolume(track.getVolume());
trackBox.addBox(tkhd);
MediaBox mdia = new MediaBox();
trackBox.addBox(mdia);
MediaHeaderBox mdhd = new MediaHeaderBox();
mdhd.setCreationTime(track.getCreationTime());
mdhd.setDuration(track.getDuration());
mdhd.setTimescale(track.getTimeScale());
mdhd.setLanguage("eng");
mdia.addBox(mdhd);
HandlerBox hdlr = new HandlerBox();
hdlr.setName(track.isAudio() ? "SoundHandle" : "VideoHandle");
hdlr.setHandlerType(track.getHandler());
mdia.addBox(hdlr);
MediaInformationBox minf = new MediaInformationBox();
minf.addBox(track.getMediaHeaderBox());
DataInformationBox dinf = new DataInformationBox();
DataReferenceBox dref = new DataReferenceBox();
dinf.addBox(dref);
DataEntryUrlBox url = new DataEntryUrlBox();
url.setFlags(1);
dref.addBox(url);
minf.addBox(dinf);
Box stbl = createStbl(track);
minf.addBox(stbl);
mdia.addBox(minf);
return trackBox;
}
protected Box createStbl(Track track) {
SampleTableBox stbl = new SampleTableBox();
createStsd(track, stbl);
createStts(track, stbl);
createStss(track, stbl);
createStsc(track, stbl);
createStsz(track, stbl);
createStco(track, stbl);
return stbl;
}
protected void createStsd(Track track, SampleTableBox stbl) {
stbl.addBox(track.getSampleDescriptionBox());
}
protected void createStts(Track track, SampleTableBox stbl) {
TimeToSampleBox.Entry lastEntry = null;
List<TimeToSampleBox.Entry> entries = new ArrayList<>();
for (long delta : track.getSampleDurations()) {
if (lastEntry != null && lastEntry.getDelta() == delta) {
lastEntry.setCount(lastEntry.getCount() + 1);
} else {
lastEntry = new TimeToSampleBox.Entry(1, delta);
entries.add(lastEntry);
}
}
TimeToSampleBox stts = new TimeToSampleBox();
stts.setEntries(entries);
stbl.addBox(stts);
}
protected void createStss(Track track, SampleTableBox stbl) {
long[] syncSamples = track.getSyncSamples();
if (syncSamples != null && syncSamples.length > 0) {
SyncSampleBox stss = new SyncSampleBox();
stss.setSampleNumber(syncSamples);
stbl.addBox(stss);
}
}
protected void createStsc(Track track, SampleTableBox stbl) {
SampleToChunkBox stsc = new SampleToChunkBox();
stsc.setEntries(new LinkedList<SampleToChunkBox.Entry>());
long lastOffset = -1;
int lastChunkNumber = 1;
int lastSampleCount = 0;
int previousWritedChunkCount = -1;
int samplesCount = track.getSamples().size();
for (int a = 0; a < samplesCount; a++) {
Sample sample = track.getSamples().get(a);
long offset = sample.getOffset();
long size = sample.getSize();
lastOffset = offset + size;
lastSampleCount++;
boolean write = false;
if (a != samplesCount - 1) {
Sample nextSample = track.getSamples().get(a + 1);
if (lastOffset != nextSample.getOffset()) {
write = true;
}
} else {
write = true;
}
if (write) {
if (previousWritedChunkCount != lastSampleCount) {
stsc.getEntries().add(new SampleToChunkBox.Entry(lastChunkNumber, lastSampleCount, 1));
previousWritedChunkCount = lastSampleCount;
}
lastSampleCount = 0;
lastChunkNumber++;
}
}
stbl.addBox(stsc);
}
protected void createStsz(Track track, SampleTableBox stbl) {
SampleSizeBox stsz = new SampleSizeBox();
stsz.setSampleSizes(track2SampleSizes.get(track));
stbl.addBox(stsz);
}
protected void createStco(Track track, SampleTableBox stbl) {
ArrayList<Long> chunksOffsets = new ArrayList<>();
long lastOffset = -1;
for (Sample sample : track.getSamples()) {
long offset = sample.getOffset();
if (lastOffset != -1 && lastOffset != offset) {
lastOffset = -1;
}
if (lastOffset == -1) {
chunksOffsets.add(offset);
}
lastOffset = offset + sample.getSize();
}
long[] chunkOffsetsLong = new long[chunksOffsets.size()];
for (int a = 0; a < chunksOffsets.size(); a++) {
chunkOffsetsLong[a] = chunksOffsets.get(a);
}
StaticChunkOffsetBox stco = new StaticChunkOffsetBox();
stco.setChunkOffsets(chunkOffsetsLong);
stbl.addBox(stco);
}
}

Просмотреть файл

@ -0,0 +1,73 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaFormat;
import com.googlecode.mp4parser.util.Matrix;
import java.io.File;
import java.util.ArrayList;
@TargetApi(16)
public class Mp4Movie {
private Matrix matrix = Matrix.ROTATE_0;
private ArrayList<Track> tracks = new ArrayList<Track>();
private File cacheFile;
private int width;
private int height;
public Matrix getMatrix() {
return matrix;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void setCacheFile(File file) {
cacheFile = file;
}
public void setRotation(int angle) {
if (angle == 0) {
matrix = Matrix.ROTATE_0;
} else if (angle == 90) {
matrix = Matrix.ROTATE_90;
} else if (angle == 180) {
matrix = Matrix.ROTATE_180;
} else if (angle == 270) {
matrix = Matrix.ROTATE_270;
}
}
public void setSize(int w, int h) {
width = w;
height = h;
}
public ArrayList<Track> getTracks() {
return tracks;
}
public File getCacheFile() {
return cacheFile;
}
public void addSample(int trackIndex, long offset, MediaCodec.BufferInfo bufferInfo) throws Exception {
if (trackIndex < 0 || trackIndex >= tracks.size()) {
return;
}
Track track = tracks.get(trackIndex);
track.addSample(offset, bufferInfo);
}
public int addTrack(MediaFormat mediaFormat, boolean isAudio) throws Exception {
tracks.add(new Track(tracks.size(), mediaFormat, isAudio));
return tracks.size() - 1;
}
}

Просмотреть файл

@ -0,0 +1,191 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
import android.annotation.TargetApi;
import android.graphics.SurfaceTexture;
import android.opengl.GLES20;
import android.view.Surface;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
@TargetApi(16)
public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
private static final int EGL_OPENGL_ES2_BIT = 4;
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private EGL10 mEGL;
private EGLDisplay mEGLDisplay = null;
private EGLContext mEGLContext = null;
private EGLSurface mEGLSurface = null;
private SurfaceTexture mSurfaceTexture;
private Surface mSurface;
private final Object mFrameSyncObject = new Object();
private boolean mFrameAvailable;
private TextureRenderer mTextureRender;
private int mWidth;
private int mHeight;
private int rotateRender = 0;
private ByteBuffer mPixelBuf;
public OutputSurface(int width, int height, int rotate) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException();
}
mWidth = width;
mHeight = height;
rotateRender = rotate;
mPixelBuf = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
mPixelBuf.order(ByteOrder.LITTLE_ENDIAN);
eglSetup(width, height);
makeCurrent();
setup();
}
public OutputSurface() {
setup();
}
private void setup() {
mTextureRender = new TextureRenderer(rotateRender);
mTextureRender.surfaceCreated();
mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
mSurfaceTexture.setOnFrameAvailableListener(this);
mSurface = new Surface(mSurfaceTexture);
}
private void eglSetup(int width, int height) {
mEGL = (EGL10) EGLContext.getEGL();
mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL10 display");
}
if (!mEGL.eglInitialize(mEGLDisplay, null)) {
mEGLDisplay = null;
throw new RuntimeException("unable to initialize EGL10");
}
int[] attribList = {
EGL10.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE, 8,
EGL10.EGL_BLUE_SIZE, 8,
EGL10.EGL_ALPHA_SIZE, 8,
EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, configs.length, numConfigs)) {
throw new RuntimeException("unable to find RGB888+pbuffer EGL config");
}
int[] attrib_list = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL10.EGL_NONE
};
mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list);
checkEglError("eglCreateContext");
if (mEGLContext == null) {
throw new RuntimeException("null context");
}
int[] surfaceAttribs = {
EGL10.EGL_WIDTH, width,
EGL10.EGL_HEIGHT, height,
EGL10.EGL_NONE
};
mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs);
checkEglError("eglCreatePbufferSurface");
if (mEGLSurface == null) {
throw new RuntimeException("surface was null");
}
}
public void release() {
if (mEGL != null) {
if (mEGL.eglGetCurrentContext().equals(mEGLContext)) {
mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
}
mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface);
mEGL.eglDestroyContext(mEGLDisplay, mEGLContext);
}
mSurface.release();
mEGLDisplay = null;
mEGLContext = null;
mEGLSurface = null;
mEGL = null;
mTextureRender = null;
mSurface = null;
mSurfaceTexture = null;
}
public void makeCurrent() {
if (mEGL == null) {
throw new RuntimeException("not configured for makeCurrent");
}
checkEglError("before makeCurrent");
if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
}
public Surface getSurface() {
return mSurface;
}
public void changeFragmentShader(String fragmentShader) {
mTextureRender.changeFragmentShader(fragmentShader);
}
public void awaitNewImage() {
final int TIMEOUT_MS = 5000;
synchronized (mFrameSyncObject) {
while (!mFrameAvailable) {
try {
mFrameSyncObject.wait(TIMEOUT_MS);
if (!mFrameAvailable) {
throw new RuntimeException("Surface frame wait timed out");
}
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
mFrameAvailable = false;
}
mTextureRender.checkGlError("before updateTexImage");
mSurfaceTexture.updateTexImage();
}
public void drawImage(boolean invert) {
mTextureRender.drawFrame(mSurfaceTexture, invert);
}
@Override
public void onFrameAvailable(SurfaceTexture st) {
synchronized (mFrameSyncObject) {
if (mFrameAvailable) {
throw new RuntimeException("mFrameAvailable already set, frame could be dropped");
}
mFrameAvailable = true;
mFrameSyncObject.notifyAll();
}
}
public ByteBuffer getFrame() {
mPixelBuf.rewind();
GLES20.glReadPixels(0, 0, mWidth, mHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
return mPixelBuf;
}
private void checkEglError(String msg) {
if (mEGL.eglGetError() != EGL10.EGL_SUCCESS) {
throw new RuntimeException("EGL error encountered (see log)");
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
public class Sample {
private long offset = 0;
private long size = 0;
public Sample(long offset, long size) {
this.offset = offset;
this.size = size;
}
public long getOffset() {
return offset;
}
public long getSize() {
return size;
}
}

Просмотреть файл

@ -0,0 +1,197 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
import android.annotation.TargetApi;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@TargetApi(16)
public class TextureRenderer {
private static final int FLOAT_SIZE_BYTES = 4;
private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
private static final float[] mTriangleVerticesData = {
-1.0f, -1.0f, 0, 0.f, 0.f,
1.0f, -1.0f, 0, 1.f, 0.f,
-1.0f, 1.0f, 0, 0.f, 1.f,
1.0f, 1.0f, 0, 1.f, 1.f,
};
private FloatBuffer mTriangleVertices;
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uSTMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
"}\n";
private static final String FRAGMENT_SHADER =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
"}\n";
private float[] mMVPMatrix = new float[16];
private float[] mSTMatrix = new float[16];
private int mProgram;
private int mTextureID = -12345;
private int muMVPMatrixHandle;
private int muSTMatrixHandle;
private int maPositionHandle;
private int maTextureHandle;
private int rotationAngle = 0;
public TextureRenderer(int rotation) {
rotationAngle = rotation;
mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangleVertices.put(mTriangleVerticesData).position(0);
Matrix.setIdentityM(mSTMatrix, 0);
}
public int getTextureId() {
return mTextureID;
}
public void drawFrame(SurfaceTexture st, boolean invert) {
checkGlError("onDrawFrame start");
st.getTransformMatrix(mSTMatrix);
if (invert) {
mSTMatrix[5] = -mSTMatrix[5];
mSTMatrix[13] = 1.0f - mSTMatrix[13];
}
GLES20.glUseProgram(mProgram);
checkGlError("glUseProgram");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(maPositionHandle);
checkGlError("glEnableVertexAttribArray maPositionHandle");
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
checkGlError("glVertexAttribPointer maTextureHandle");
GLES20.glEnableVertexAttribArray(maTextureHandle);
checkGlError("glEnableVertexAttribArray maTextureHandle");
GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
checkGlError("glDrawArrays");
GLES20.glFinish();
}
public void surfaceCreated() {
mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
if (mProgram == 0) {
throw new RuntimeException("failed creating program");
}
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
if (maPositionHandle == -1) {
throw new RuntimeException("Could not get attrib location for aPosition");
}
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
checkGlError("glGetAttribLocation aTextureCoord");
if (maTextureHandle == -1) {
throw new RuntimeException("Could not get attrib location for aTextureCoord");
}
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix");
if (muMVPMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uMVPMatrix");
}
muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
checkGlError("glGetUniformLocation uSTMatrix");
if (muSTMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uSTMatrix");
}
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
mTextureID = textures[0];
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
checkGlError("glBindTexture mTextureID");
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
checkGlError("glTexParameter");
Matrix.setIdentityM(mMVPMatrix, 0);
if (rotationAngle != 0) {
Matrix.rotateM(mMVPMatrix, 0, rotationAngle, 0, 0, 1);
}
}
public void changeFragmentShader(String fragmentShader) {
GLES20.glDeleteProgram(mProgram);
mProgram = createProgram(VERTEX_SHADER, fragmentShader);
if (mProgram == 0) {
throw new RuntimeException("failed creating program");
}
}
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
checkGlError("glCreateShader type=" + shaderType);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
int program = GLES20.glCreateProgram();
checkGlError("glCreateProgram");
if (program == 0) {
return 0;
}
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
GLES20.glDeleteProgram(program);
program = 0;
}
return program;
}
public void checkGlError(String op) {
int error;
if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
throw new RuntimeException(op + ": glError " + error);
}
}
}

Просмотреть файл

@ -0,0 +1,255 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaFormat;
import com.coremedia.iso.boxes.AbstractMediaHeaderBox;
import com.coremedia.iso.boxes.SampleDescriptionBox;
import com.coremedia.iso.boxes.SoundMediaHeaderBox;
import com.coremedia.iso.boxes.VideoMediaHeaderBox;
import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor;
import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.SLConfigDescriptor;
import com.mp4parser.iso14496.part15.AvcConfigurationBox;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
@TargetApi(16)
public class Track {
private long trackId = 0;
private ArrayList<Sample> samples = new ArrayList<Sample>();
private long duration = 0;
private String handler;
private AbstractMediaHeaderBox headerBox = null;
private SampleDescriptionBox sampleDescriptionBox = null;
private LinkedList<Integer> syncSamples = null;
private int timeScale;
private Date creationTime = new Date();
private int height;
private int width;
private float volume = 0;
private ArrayList<Long> sampleDurations = new ArrayList<Long>();
private boolean isAudio = false;
private static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<Integer, Integer>();
private long lastPresentationTimeUs = 0;
private boolean first = true;
static {
samplingFrequencyIndexMap.put(96000, 0x0);
samplingFrequencyIndexMap.put(88200, 0x1);
samplingFrequencyIndexMap.put(64000, 0x2);
samplingFrequencyIndexMap.put(48000, 0x3);
samplingFrequencyIndexMap.put(44100, 0x4);
samplingFrequencyIndexMap.put(32000, 0x5);
samplingFrequencyIndexMap.put(24000, 0x6);
samplingFrequencyIndexMap.put(22050, 0x7);
samplingFrequencyIndexMap.put(16000, 0x8);
samplingFrequencyIndexMap.put(12000, 0x9);
samplingFrequencyIndexMap.put(11025, 0xa);
samplingFrequencyIndexMap.put(8000, 0xb);
}
public Track(int id, MediaFormat format, boolean isAudio) throws Exception {
trackId = id;
if (!isAudio) {
sampleDurations.add((long)3015);
duration = 3015;
width = format.getInteger(MediaFormat.KEY_WIDTH);
height = format.getInteger(MediaFormat.KEY_HEIGHT);
timeScale = 90000;
syncSamples = new LinkedList<Integer>();
handler = "vide";
headerBox = new VideoMediaHeaderBox();
sampleDescriptionBox = new SampleDescriptionBox();
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.equals("video/avc")) {
VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1");
visualSampleEntry.setDataReferenceIndex(1);
visualSampleEntry.setDepth(24);
visualSampleEntry.setFrameCount(1);
visualSampleEntry.setHorizresolution(72);
visualSampleEntry.setVertresolution(72);
visualSampleEntry.setWidth(width);
visualSampleEntry.setHeight(height);
AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox();
if (format.getByteBuffer("csd-0") != null) {
ArrayList<byte[]> spsArray = new ArrayList<byte[]>();
ByteBuffer spsBuff = format.getByteBuffer("csd-0");
spsBuff.position(4);
byte[] spsBytes = new byte[spsBuff.remaining()];
spsBuff.get(spsBytes);
spsArray.add(spsBytes);
ArrayList<byte[]> ppsArray = new ArrayList<byte[]>();
ByteBuffer ppsBuff = format.getByteBuffer("csd-1");
ppsBuff.position(4);
byte[] ppsBytes = new byte[ppsBuff.remaining()];
ppsBuff.get(ppsBytes);
ppsArray.add(ppsBytes);
avcConfigurationBox.setSequenceParameterSets(spsArray);
avcConfigurationBox.setPictureParameterSets(ppsArray);
}
//ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(spsBytes);
//SeqParameterSet seqParameterSet = SeqParameterSet.read(byteArrayInputStream);
avcConfigurationBox.setAvcLevelIndication(13);
avcConfigurationBox.setAvcProfileIndication(100);
avcConfigurationBox.setBitDepthLumaMinus8(-1);
avcConfigurationBox.setBitDepthChromaMinus8(-1);
avcConfigurationBox.setChromaFormat(-1);
avcConfigurationBox.setConfigurationVersion(1);
avcConfigurationBox.setLengthSizeMinusOne(3);
avcConfigurationBox.setProfileCompatibility(0);
visualSampleEntry.addBox(avcConfigurationBox);
sampleDescriptionBox.addBox(visualSampleEntry);
} else if (mime.equals("video/mp4v")) {
VisualSampleEntry visualSampleEntry = new VisualSampleEntry("mp4v");
visualSampleEntry.setDataReferenceIndex(1);
visualSampleEntry.setDepth(24);
visualSampleEntry.setFrameCount(1);
visualSampleEntry.setHorizresolution(72);
visualSampleEntry.setVertresolution(72);
visualSampleEntry.setWidth(width);
visualSampleEntry.setHeight(height);
sampleDescriptionBox.addBox(visualSampleEntry);
}
} else {
sampleDurations.add((long)1024);
duration = 1024;
isAudio = true;
volume = 1;
timeScale = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
handler = "soun";
headerBox = new SoundMediaHeaderBox();
sampleDescriptionBox = new SampleDescriptionBox();
AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a");
audioSampleEntry.setChannelCount(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
audioSampleEntry.setSampleRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
audioSampleEntry.setDataReferenceIndex(1);
audioSampleEntry.setSampleSize(16);
ESDescriptorBox esds = new ESDescriptorBox();
ESDescriptor descriptor = new ESDescriptor();
descriptor.setEsId(0);
SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor();
slConfigDescriptor.setPredefined(2);
descriptor.setSlConfigDescriptor(slConfigDescriptor);
DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor();
decoderConfigDescriptor.setObjectTypeIndication(0x40);
decoderConfigDescriptor.setStreamType(5);
decoderConfigDescriptor.setBufferSizeDB(1536);
decoderConfigDescriptor.setMaxBitRate(96000);
decoderConfigDescriptor.setAvgBitRate(96000);
AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig();
audioSpecificConfig.setAudioObjectType(2);
audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get((int)audioSampleEntry.getSampleRate()));
audioSpecificConfig.setChannelConfiguration(audioSampleEntry.getChannelCount());
decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig);
descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor);
ByteBuffer data = descriptor.serialize();
esds.setEsDescriptor(descriptor);
esds.setData(data);
audioSampleEntry.addBox(esds);
sampleDescriptionBox.addBox(audioSampleEntry);
}
}
public long getTrackId() {
return trackId;
}
public void addSample(long offset, MediaCodec.BufferInfo bufferInfo) {
boolean isSyncFrame = !isAudio && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
samples.add(new Sample(offset, bufferInfo.size));
if (syncSamples != null && isSyncFrame) {
syncSamples.add(samples.size());
}
long delta = bufferInfo.presentationTimeUs - lastPresentationTimeUs;
lastPresentationTimeUs = bufferInfo.presentationTimeUs;
delta = (delta * timeScale + 500000L) / 1000000L;
if (!first) {
sampleDurations.add(sampleDurations.size() - 1, delta);
duration += delta;
}
first = false;
}
public ArrayList<Sample> getSamples() {
return samples;
}
public long getDuration() {
return duration;
}
public String getHandler() {
return handler;
}
public AbstractMediaHeaderBox getMediaHeaderBox() {
return headerBox;
}
public SampleDescriptionBox getSampleDescriptionBox() {
return sampleDescriptionBox;
}
public long[] getSyncSamples() {
if (syncSamples == null || syncSamples.isEmpty()) {
return null;
}
long[] returns = new long[syncSamples.size()];
for (int i = 0; i < syncSamples.size(); i++) {
returns[i] = syncSamples.get(i);
}
return returns;
}
public int getTimeScale() {
return timeScale;
}
public Date getCreationTime() {
return creationTime;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public float getVolume() {
return volume;
}
public ArrayList<Long> getSampleDurations() {
return sampleDurations;
}
public boolean isAudio() {
return isAudio;
}
}

Просмотреть файл

@ -0,0 +1,71 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
import android.os.AsyncTask;
import android.util.Log;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableEmitter;
import kotlin.Unit;
public class VideoCompress {
public static Observable<Boolean> compressVideoHigh(String srcPath, String destPath) {
return Observable.create(emitter -> {
VideoCompressTask task = new VideoCompressTask(emitter, VideoController.COMPRESS_QUALITY_HIGH);
task.execute(srcPath, destPath);
});
}
public static Observable<Boolean> compressVideoMedium(String srcPath, String destPath) {
return Observable.create(emitter -> {
VideoCompressTask task = new VideoCompressTask(emitter, VideoController.COMPRESS_QUALITY_MEDIUM);
task.execute(srcPath, destPath);
});
}
public static Observable<Boolean> compressVideoLow(String srcPath, String destPath) {
return Observable.create(emitter -> {
VideoCompressTask task = new VideoCompressTask(emitter, VideoController.COMPRESS_QUALITY_LOW);
task.execute(srcPath, destPath);
});
}
private static class VideoCompressTask extends AsyncTask<String, Float, Boolean> {
private ObservableEmitter<Boolean> emitter;
private int mQuality;
public VideoCompressTask(ObservableEmitter<Boolean> listener, int quality) {
emitter = listener;
mQuality = quality;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Boolean doInBackground(String... paths) {
Log.i("VideoController", "Entered doInBackground");
return VideoController.getInstance().convertVideo(paths[0], paths[1], mQuality, new VideoController.CompressProgressListener() {
@Override
public void onProgress(float percent) {
publishProgress(percent);
}
});
}
@Override
protected void onProgressUpdate(Float... percent) {
super.onProgressUpdate(percent);
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (emitter != null) {
emitter.onNext(result);
}
}
}
}

Просмотреть файл

@ -0,0 +1,781 @@
package com.ttpsc.dynamics365fieldService.core.managers.VideoCompressor;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@SuppressLint("NewApi")
public class VideoController {
static final int COMPRESS_QUALITY_HIGH = 1;
static final int COMPRESS_QUALITY_MEDIUM = 2;
static final int COMPRESS_QUALITY_LOW = 3;
public static File cachedFile;
public String path;
public final static String MIME_TYPE = "video/avc";
private final static int PROCESSOR_TYPE_OTHER = 0;
private final static int PROCESSOR_TYPE_QCOM = 1;
private final static int PROCESSOR_TYPE_INTEL = 2;
private final static int PROCESSOR_TYPE_MTK = 3;
private final static int PROCESSOR_TYPE_SEC = 4;
private final static int PROCESSOR_TYPE_TI = 5;
private static volatile VideoController Instance = null;
private boolean videoConvertFirstWrite = true;
interface CompressProgressListener {
void onProgress(float percent);
}
public static VideoController getInstance() {
VideoController localInstance = Instance;
if (localInstance == null) {
synchronized (VideoController.class) {
localInstance = Instance;
if (localInstance == null) {
Instance = localInstance = new VideoController();
}
}
}
return localInstance;
}
@SuppressLint("NewApi")
public static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
int lastColorFormat = 0;
for (int i = 0; i < capabilities.colorFormats.length; i++) {
int colorFormat = capabilities.colorFormats[i];
if (isRecognizedFormat(colorFormat)) {
lastColorFormat = colorFormat;
if (!(codecInfo.getName().equals("OMX.SEC.AVC.Encoder") && colorFormat == 19)) {
return colorFormat;
}
}
}
return lastColorFormat;
}
private static boolean isRecognizedFormat(int colorFormat) {
switch (colorFormat) {
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
return true;
default:
return false;
}
}
public native static int convertVideoFrame(ByteBuffer src, ByteBuffer dest, int destFormat, int width, int height, int padding, int swap);
private void didWriteData(final boolean last, final boolean error) {
final boolean firstWrite = videoConvertFirstWrite;
if (firstWrite) {
videoConvertFirstWrite = false;
}
}
public static class VideoConvertRunnable implements Runnable {
private String videoPath;
private String destPath;
private VideoConvertRunnable(String videoPath, String destPath) {
this.videoPath = videoPath;
this.destPath = destPath;
}
public static void runConversion(final String videoPath, final String destPath) {
new Thread(new Runnable() {
@Override
public void run() {
try {
VideoConvertRunnable wrapper = new VideoConvertRunnable(videoPath, destPath);
Thread th = new Thread(wrapper, "VideoConvertRunnable");
th.start();
th.join();
} catch (Exception e) {
Log.e("tmessages", e.getMessage());
}
}
}).start();
}
@Override
public void run() {
VideoController.getInstance().convertVideo(videoPath, destPath, 0, null);
}
}
public static MediaCodecInfo selectCodec(String mimeType) {
int numCodecs = MediaCodecList.getCodecCount();
MediaCodecInfo lastCodecInfo = null;
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
continue;
}
String[] types = codecInfo.getSupportedTypes();
for (String type : types) {
if (type.equalsIgnoreCase(mimeType)) {
lastCodecInfo = codecInfo;
if (!lastCodecInfo.getName().equals("OMX.SEC.avc.enc")) {
return lastCodecInfo;
} else if (lastCodecInfo.getName().equals("OMX.SEC.AVC.Encoder")) {
return lastCodecInfo;
}
}
}
}
return lastCodecInfo;
}
/**
* Background conversion for queueing tasks
* @param path source file to compress
* @param dest destination directory to put result
*/
public void scheduleVideoConvert(String path, String dest) {
startVideoConvertFromQueue(path, dest);
}
private void startVideoConvertFromQueue(String path, String dest) {
VideoConvertRunnable.runConversion(path, dest);
}
@TargetApi(16)
private long readAndWriteTrack(MediaExtractor extractor, MP4Builder mediaMuxer, MediaCodec.BufferInfo info, long start, long end, File file, boolean isAudio) throws Exception {
int trackIndex = selectTrack(extractor, isAudio);
if (trackIndex >= 0) {
extractor.selectTrack(trackIndex);
MediaFormat trackFormat = extractor.getTrackFormat(trackIndex);
int muxerTrackIndex = mediaMuxer.addTrack(trackFormat, isAudio);
int maxBufferSize = trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
boolean inputDone = false;
if (start > 0) {
extractor.seekTo(start, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else {
extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
}
ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
long startTime = -1;
while (!inputDone) {
boolean eof = false;
int index = extractor.getSampleTrackIndex();
if (index == trackIndex) {
info.size = extractor.readSampleData(buffer, 0);
if (info.size < 0) {
info.size = 0;
eof = true;
} else {
info.presentationTimeUs = extractor.getSampleTime();
if (start > 0 && startTime == -1) {
startTime = info.presentationTimeUs;
}
if (end < 0 || info.presentationTimeUs < end) {
info.offset = 0;
info.flags = extractor.getSampleFlags();
if (mediaMuxer.writeSampleData(muxerTrackIndex, buffer, info, isAudio)) {
// didWriteData(messageObject, file, false, false);
}
extractor.advance();
} else {
eof = true;
}
}
} else if (index == -1) {
eof = true;
}
if (eof) {
inputDone = true;
}
}
extractor.unselectTrack(trackIndex);
return startTime;
}
return -1;
}
@TargetApi(16)
private int selectTrack(MediaExtractor extractor, boolean audio) {
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (audio) {
if (mime.startsWith("audio/")) {
return i;
}
} else {
if (mime.startsWith("video/")) {
return i;
}
}
}
return -5;
}
/**
* Perform the actual video compression. Processes the frames and does the magic
* @param sourcePath the source uri for the file as per
* @param destinationPath the destination directory where compressed video is eventually saved
* @return
*/
@TargetApi(16)
public boolean convertVideo(final String sourcePath, String destinationPath, int quality, CompressProgressListener listener) {
this.path=sourcePath;
Log.i("VideoController", "Entered convertVideo");
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
String width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
String height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
String rotation = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
long duration = Long.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000;
long startTime = -1;
long endTime = -1;
int rotationValue = Integer.valueOf(rotation);
int originalWidth = Integer.valueOf(width);
int originalHeight = Integer.valueOf(height);
int resultWidth;
int resultHeight;
int bitrate;
switch (quality) {
default:
case COMPRESS_QUALITY_HIGH:
resultWidth = originalWidth * 2 / 3;
resultHeight = originalHeight * 2 / 3;
bitrate = resultWidth * resultHeight * 30;
break;
case COMPRESS_QUALITY_MEDIUM:
resultWidth = originalWidth / 2;
resultHeight = originalHeight / 2;
bitrate = resultWidth * resultHeight * 10;
break;
case COMPRESS_QUALITY_LOW:
resultWidth = originalWidth / 2;
resultHeight = originalHeight / 2;
bitrate = (resultWidth/2) * (resultHeight/2) * 10;
break;
}
int rotateRender = 0;
File cacheFile = new File(destinationPath);
if (Build.VERSION.SDK_INT < 18 && resultHeight > resultWidth && resultWidth != originalWidth && resultHeight != originalHeight) {
int temp = resultHeight;
resultHeight = resultWidth;
resultWidth = temp;
rotationValue = 90;
rotateRender = 270;
} else if (Build.VERSION.SDK_INT > 20) {
if (rotationValue == 90) {
int temp = resultHeight;
resultHeight = resultWidth;
resultWidth = temp;
rotationValue = 0;
rotateRender = 270;
} else if (rotationValue == 180) {
rotateRender = 180;
rotationValue = 0;
} else if (rotationValue == 270) {
int temp = resultHeight;
resultHeight = resultWidth;
resultWidth = temp;
rotationValue = 0;
rotateRender = 90;
}
}
File inputFile = new File(path);
if (!inputFile.canRead()) {
didWriteData(true, true);
return false;
}
videoConvertFirstWrite = true;
boolean error = false;
long videoStartTime = startTime;
long time = System.currentTimeMillis();
if (resultWidth != 0 && resultHeight != 0) {
MP4Builder mediaMuxer = null;
MediaExtractor extractor = null;
try {
Log.i("VideoController", "Entered try");
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
Mp4Movie movie = new Mp4Movie();
movie.setCacheFile(cacheFile);
movie.setRotation(rotationValue);
movie.setSize(resultWidth, resultHeight);
mediaMuxer = new MP4Builder().createMovie(movie);
extractor = new MediaExtractor();
extractor.setDataSource(inputFile.toString());
if (resultWidth != originalWidth || resultHeight != originalHeight) {
int videoIndex;
videoIndex = selectTrack(extractor, false);
if (videoIndex >= 0) {
MediaCodec decoder = null;
MediaCodec encoder = null;
InputSurface inputSurface = null;
OutputSurface outputSurface = null;
try {
long videoTime = -1;
boolean outputDone = false;
boolean inputDone = false;
boolean decoderDone = false;
int swapUV = 0;
int videoTrackIndex = -5;
int colorFormat;
int processorType = PROCESSOR_TYPE_OTHER;
String manufacturer = Build.MANUFACTURER.toLowerCase();
if (Build.VERSION.SDK_INT < 18) {
MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
colorFormat = selectColorFormat(codecInfo, MIME_TYPE);
if (colorFormat == 0) {
throw new RuntimeException("no supported color format");
}
String codecName = codecInfo.getName();
if (codecName.contains("OMX.qcom.")) {
processorType = PROCESSOR_TYPE_QCOM;
if (Build.VERSION.SDK_INT == 16) {
if (manufacturer.equals("lge") || manufacturer.equals("nokia")) {
swapUV = 1;
}
}
} else if (codecName.contains("OMX.Intel.")) {
processorType = PROCESSOR_TYPE_INTEL;
} else if (codecName.equals("OMX.MTK.VIDEO.ENCODER.AVC")) {
processorType = PROCESSOR_TYPE_MTK;
} else if (codecName.equals("OMX.SEC.AVC.Encoder")) {
processorType = PROCESSOR_TYPE_SEC;
swapUV = 1;
} else if (codecName.equals("OMX.TI.DUCATI1.VIDEO.H264E")) {
processorType = PROCESSOR_TYPE_TI;
}
Log.e("tmessages", "codec = " + codecInfo.getName() + " manufacturer = " + manufacturer + "device = " + Build.MODEL);
} else {
colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
}
Log.e("tmessages", "colorFormat = " + colorFormat);
int resultHeightAligned = resultHeight;
int padding = 0;
int bufferSize = resultWidth * resultHeight * 3 / 2;
if (processorType == PROCESSOR_TYPE_OTHER) {
if (resultHeight % 16 != 0) {
resultHeightAligned += (16 - (resultHeight % 16));
padding = resultWidth * (resultHeightAligned - resultHeight);
bufferSize += padding * 5 / 4;
}
} else if (processorType == PROCESSOR_TYPE_QCOM) {
if (!manufacturer.toLowerCase().equals("lge")) {
int uvoffset = (resultWidth * resultHeight + 2047) & ~2047;
padding = uvoffset - (resultWidth * resultHeight);
bufferSize += padding;
}
} else if (processorType == PROCESSOR_TYPE_TI) {
//resultHeightAligned = 368;
//bufferSize = resultWidth * resultHeightAligned * 3 / 2;
//resultHeightAligned += (16 - (resultHeight % 16));
//padding = resultWidth * (resultHeightAligned - resultHeight);
//bufferSize += padding * 5 / 4;
} else if (processorType == PROCESSOR_TYPE_MTK) {
if (manufacturer.equals("baidu")) {
resultHeightAligned += (16 - (resultHeight % 16));
padding = resultWidth * (resultHeightAligned - resultHeight);
bufferSize += padding * 5 / 4;
}
}
extractor.selectTrack(videoIndex);
if (startTime > 0) {
extractor.seekTo(startTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else {
extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
}
MediaFormat inputFormat = extractor.getTrackFormat(videoIndex);
MediaFormat outputFormat = MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight);
outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
if (Build.VERSION.SDK_INT < 18) {
outputFormat.setInteger("stride", resultWidth + 32);
outputFormat.setInteger("slice-height", resultHeight);
}
encoder = MediaCodec.createEncoderByType(MIME_TYPE);
encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
if (Build.VERSION.SDK_INT >= 18) {
inputSurface = new InputSurface(encoder.createInputSurface());
inputSurface.makeCurrent();
}
encoder.start();
decoder = MediaCodec.createDecoderByType(inputFormat.getString(MediaFormat.KEY_MIME));
if (Build.VERSION.SDK_INT >= 18) {
outputSurface = new OutputSurface();
} else {
outputSurface = new OutputSurface(resultWidth, resultHeight, rotateRender);
}
decoder.configure(inputFormat, outputSurface.getSurface(), null, 0);
decoder.start();
final int TIMEOUT_USEC = 2500;
ByteBuffer[] decoderInputBuffers = null;
ByteBuffer[] encoderOutputBuffers = null;
ByteBuffer[] encoderInputBuffers = null;
if (Build.VERSION.SDK_INT < 21) {
decoderInputBuffers = decoder.getInputBuffers();
encoderOutputBuffers = encoder.getOutputBuffers();
if (Build.VERSION.SDK_INT < 18) {
encoderInputBuffers = encoder.getInputBuffers();
}
}
while (!outputDone) {
if (!inputDone) {
boolean eof = false;
int index = extractor.getSampleTrackIndex();
if (index == videoIndex) {
int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
if (inputBufIndex >= 0) {
ByteBuffer inputBuf;
if (Build.VERSION.SDK_INT < 21) {
inputBuf = decoderInputBuffers[inputBufIndex];
} else {
inputBuf = decoder.getInputBuffer(inputBufIndex);
}
int chunkSize = extractor.readSampleData(inputBuf, 0);
if (chunkSize < 0) {
decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
inputDone = true;
} else {
decoder.queueInputBuffer(inputBufIndex, 0, chunkSize, extractor.getSampleTime(), 0);
extractor.advance();
}
}
} else if (index == -1) {
eof = true;
}
if (eof) {
int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
if (inputBufIndex >= 0) {
decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
inputDone = true;
}
}
}
boolean decoderOutputAvailable = !decoderDone;
boolean encoderOutputAvailable = true;
while (decoderOutputAvailable || encoderOutputAvailable) {
int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
encoderOutputAvailable = false;
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
if (Build.VERSION.SDK_INT < 21) {
encoderOutputBuffers = encoder.getOutputBuffers();
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = encoder.getOutputFormat();
if (videoTrackIndex == -5) {
videoTrackIndex = mediaMuxer.addTrack(newFormat, false);
}
} else if (encoderStatus < 0) {
throw new RuntimeException("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
} else {
ByteBuffer encodedData;
if (Build.VERSION.SDK_INT < 21) {
encodedData = encoderOutputBuffers[encoderStatus];
} else {
encodedData = encoder.getOutputBuffer(encoderStatus);
}
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
if (info.size > 1) {
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
if (mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info, false)) {
didWriteData(false, false);
}
} else if (videoTrackIndex == -5) {
byte[] csd = new byte[info.size];
encodedData.limit(info.offset + info.size);
encodedData.position(info.offset);
encodedData.get(csd);
ByteBuffer sps = null;
ByteBuffer pps = null;
for (int a = info.size - 1; a >= 0; a--) {
if (a > 3) {
if (csd[a] == 1 && csd[a - 1] == 0 && csd[a - 2] == 0 && csd[a - 3] == 0) {
sps = ByteBuffer.allocate(a - 3);
pps = ByteBuffer.allocate(info.size - (a - 3));
sps.put(csd, 0, a - 3).position(0);
pps.put(csd, a - 3, info.size - (a - 3)).position(0);
break;
}
} else {
break;
}
}
MediaFormat newFormat = MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight);
if (sps != null && pps != null) {
newFormat.setByteBuffer("csd-0", sps);
newFormat.setByteBuffer("csd-1", pps);
}
videoTrackIndex = mediaMuxer.addTrack(newFormat, false);
}
}
outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
encoder.releaseOutputBuffer(encoderStatus, false);
}
if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
continue;
}
if (!decoderDone) {
int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
decoderOutputAvailable = false;
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = decoder.getOutputFormat();
Log.e("tmessages", "newFormat = " + newFormat);
} else if (decoderStatus < 0) {
throw new RuntimeException("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
} else {
boolean doRender;
if (Build.VERSION.SDK_INT >= 18) {
doRender = info.size != 0;
} else {
doRender = info.size != 0 || info.presentationTimeUs != 0;
}
if (endTime > 0 && info.presentationTimeUs >= endTime) {
inputDone = true;
decoderDone = true;
doRender = false;
info.flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
}
if (startTime > 0 && videoTime == -1) {
if (info.presentationTimeUs < startTime) {
doRender = false;
Log.e("tmessages", "drop frame startTime = " + startTime + " present time = " + info.presentationTimeUs);
} else {
videoTime = info.presentationTimeUs;
}
}
decoder.releaseOutputBuffer(decoderStatus, doRender);
if (doRender) {
boolean errorWait = false;
try {
outputSurface.awaitNewImage();
} catch (Exception e) {
errorWait = true;
Log.e("tmessages", e.getMessage());
}
if (!errorWait) {
if (Build.VERSION.SDK_INT >= 18) {
outputSurface.drawImage(false);
inputSurface.setPresentationTime(info.presentationTimeUs * 1000);
if (listener != null) {
listener.onProgress((float) info.presentationTimeUs / (float) duration * 100);
}
inputSurface.swapBuffers();
} else {
int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
if (inputBufIndex >= 0) {
outputSurface.drawImage(true);
ByteBuffer rgbBuf = outputSurface.getFrame();
ByteBuffer yuvBuf = encoderInputBuffers[inputBufIndex];
yuvBuf.clear();
convertVideoFrame(rgbBuf, yuvBuf, colorFormat, resultWidth, resultHeight, padding, swapUV);
encoder.queueInputBuffer(inputBufIndex, 0, bufferSize, info.presentationTimeUs, 0);
} else {
Log.e("tmessages", "input buffer not available");
}
}
}
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
decoderOutputAvailable = false;
Log.e("tmessages", "decoder stream end");
if (Build.VERSION.SDK_INT >= 18) {
encoder.signalEndOfInputStream();
} else {
int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
if (inputBufIndex >= 0) {
encoder.queueInputBuffer(inputBufIndex, 0, 1, info.presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
}
}
}
}
}
if (videoTime != -1) {
videoStartTime = videoTime;
}
} catch (Exception e) {
Log.e("tmessages", e.getMessage());
error = true;
}
extractor.unselectTrack(videoIndex);
if (outputSurface != null) {
outputSurface.release();
}
if (inputSurface != null) {
inputSurface.release();
}
if (decoder != null) {
decoder.stop();
decoder.release();
}
if (encoder != null) {
encoder.stop();
encoder.release();
}
}
} else {
long videoTime = readAndWriteTrack(extractor, mediaMuxer, info, startTime, endTime, cacheFile, false);
if (videoTime != -1) {
videoStartTime = videoTime;
}
}
if (!error) {
readAndWriteTrack(extractor, mediaMuxer, info, videoStartTime, endTime, cacheFile, true);
}
} catch (Exception e) {
error = true;
Log.e("tmessages", e.getMessage());
} finally {
if (extractor != null) {
extractor.release();
}
if (mediaMuxer != null) {
try {
mediaMuxer.finishMovie(false);
} catch (Exception e) {
Log.e("tmessages", e.getMessage());
}
}
Log.e("tmessages", "time = " + (System.currentTimeMillis() - time));
}
} else {
didWriteData(true, true);
return false;
}
didWriteData(true, error);
cachedFile=cacheFile;
/* File fdelete = inputFile;
if (fdelete.exists()) {
if (fdelete.delete()) {
Log.e("file Deleted :" ,inputFile.getPath());
} else {
Log.e("file not Deleted :" , inputFile.getPath());
}
}*/
//inputFile.delete();
Log.e("ViratPath",path+"");
Log.e("ViratPath",cacheFile.getPath()+"");
Log.e("ViratPath",inputFile.getPath()+"");
/* Log.e("ViratPath",path+"");
File replacedFile = new File(path);
FileOutputStream fos = null;
InputStream inputStream = null;
try {
fos = new FileOutputStream(replacedFile);
inputStream = new FileInputStream(cacheFile);
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
fos.write(buf, 0, len);
}
inputStream.close();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
*/
// cacheFile.delete();
/* try {
// copyFile(cacheFile,inputFile);
//inputFile.delete();
FileUtils.copyFile(cacheFile,inputFile);
} catch (IOException e) {
e.printStackTrace();
}*/
// cacheFile.delete();
// inputFile.delete();
return true;
}
public static void copyFile(File src, File dst) throws IOException
{
FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel();
try
{
inChannel.transferTo(1, inChannel.size(), outChannel);
}
finally
{
if (inChannel != null)
inChannel.close();
if (outChannel != null)
outChannel.close();
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
package com.ttpsc.dynamics365fieldService.dal.abstraction
import com.ttpsc.dynamics365fieldService.AppConfiguration
import com.ttpsc.dynamics365fieldService.dal.models.dynamics.*
import com.ttpsc.dynamics365fieldService.dal.models.dynamics.customFields.FormWrapper
import io.reactivex.rxjava3.core.Observable
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.*
interface DynamicsFieldServiceApi {
@GET("bookableresources")
fun getBookableResources(@QueryMap options: Map<String, String>?): Observable<DalBaseDynamicsModel<DalBookableResource>>
@GET("bookableresourcebookings")
fun getBookableResourceBookings(@Header("Prefer") itemsPerPageCountHeader: String?, @QueryMap options: Map<String, String>?): Observable<DalBaseDynamicsModel<DalBookableResourceBooking>>
@GET("bookingstatuses")
fun getBookingStatuses(): Observable<DalBaseDynamicsModel<DalDynamicsBookingStatus>>
@GET("msdyn_workorders")
fun getWorkOrders(@QueryMap options: Map<String, String>?): Observable<DalBaseDynamicsModel<DalDynamicsWorkOrder>>
@GET("systemusers")
fun getUserInfo(@QueryMap options: Map<String, String>?): Observable<DalBaseDynamicsModel<DalDynamicsUserInfo>>
@GET("msdyn_iotalerts")
fun getAlerts(@QueryMap options: Map<String, String>?): Observable<DalBaseDynamicsModel<DalDynamicsAlert>>
@PUT("bookableresourcebookings({bookableResourceBookingId})/BookingStatus/\$ref")
fun changeBookableResourceBookingStatus(@Path("bookableResourceBookingId") bookableResourceBookingId: String, @Body bookingStatusBody: Map<String, String>): Observable<Response<Void>>
@GET("annotations")
fun getNotes(@QueryMap options: Map<String, String>?): Observable<DalBaseDynamicsModel<DalNote>>
@POST("annotations")
fun createNote(@Body note: DalCreateNoteRequestBody): Observable<Response<Void>>
@GET("systemforms")
fun downloadForm(@QueryMap options: Map<String, String>?): Observable<DalBaseDynamicsModel<FormWrapper>>
@GET("msdyn_workorders")
fun getRawWorkOrders(@QueryMap options: Map<String, String>?): Observable<Response<String>>
@GET
fun downloadFile(
@Url fileUrl: String?, @Tag leaveUrl: String = AppConfiguration.RequestTags.leaveUrl
): Observable<ResponseBody?>
}

Просмотреть файл

@ -0,0 +1,14 @@
package com.ttpsc.dynamics365fieldService.dal.models.dynamics
import com.google.gson.annotations.SerializedName
class DalBaseDynamicsModel <DataType>(
@SerializedName("@odata.context")
val dataContext: String,
val value : List<DataType>,
@SerializedName("@odata.count")
val count: Int?,
@SerializedName("@odata.nextLink")
val nextPageLink: String?
)

Просмотреть файл

@ -0,0 +1,22 @@
package com.ttpsc.dynamics365fieldService.dal.models.dynamics
import com.google.gson.annotations.SerializedName
import com.ttpsc.dynamics365fieldService.bll.models.BookableResource
import com.ttpsc.dynamics365fieldService.bll.models.Procedure
class DalBookableResource(
@SerializedName("@odata.etag") val etag: String,
@SerializedName("_userid_value") val _userid_value: String,
@SerializedName("bookableresourceid") val bookableresourceid: String
){
fun toBookableResource(): BookableResource {
val bookableResource = BookableResource(
userId = _userid_value,
bookableResourceId = bookableresourceid
)
return bookableResource
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше