diff --git a/translate/final/app/build.gradle b/translate/final/app/build.gradle new file mode 100644 index 0000000..8135786 --- /dev/null +++ b/translate/final/app/build.gradle @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.google.mlkit.codelab.translate" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + 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:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.3.0' + implementation 'androidx.fragment:fragment-ktx:1.2.4' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + // Add CameraX dependencies + def camerax_version = "1.0.0-beta10" + implementation "androidx.camera:camera-core:${camerax_version}" + implementation "androidx.camera:camera-camera2:${camerax_version}" + implementation "androidx.camera:camera-lifecycle:${camerax_version}" + implementation "androidx.camera:camera-view:1.0.0-alpha17" + + // Add ML Kit dependencies + implementation 'com.google.android.gms:play-services-mlkit-text-recognition:16.1.1' + implementation 'com.google.mlkit:language-id:16.1.1' + implementation 'com.google.mlkit:translate:16.1.1' +} diff --git a/translate/final/app/proguard-rules.pro b/translate/final/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/translate/final/app/proguard-rules.pro @@ -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 diff --git a/translate/final/app/src/main/AndroidManifest.xml b/translate/final/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8ed4db2 --- /dev/null +++ b/translate/final/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/MainActivity.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/MainActivity.kt new file mode 100644 index 0000000..006e0cb --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/MainActivity.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.mlkit.codelab.translate + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import com.google.mlkit.codelab.translate.main.MainFragment + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.main_activity) + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction() + .replace(R.id.container, MainFragment.newInstance()) + .commitNow() + } + } + +} diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/analyzer/TextAnalyzer.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/analyzer/TextAnalyzer.kt new file mode 100644 index 0000000..07a875b --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/analyzer/TextAnalyzer.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.mlkit.codelab.translate.analyzer + +import android.content.Context +import android.graphics.Rect +import android.util.Log +import android.widget.Toast +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.MutableLiveData +import com.google.android.gms.tasks.Task +import com.google.mlkit.common.MlKitException +import com.google.mlkit.codelab.translate.util.ImageUtils +import com.google.mlkit.vision.common.InputImage +import com.google.mlkit.vision.text.Text +import com.google.mlkit.vision.text.TextRecognition +import java.lang.Exception + +/** + * Analyzes the frames passed in from the camera and returns any detected text within the requested + * crop region. + */ +class TextAnalyzer( + private val context: Context, + private val lifecycle: Lifecycle, + private val result: MutableLiveData, + private val imageCropPercentages: MutableLiveData> +) : ImageAnalysis.Analyzer { + + // TODO: Instantiate TextRecognition detector + + // TODO: Add lifecycle observer to properly close ML Kit detectors + + @androidx.camera.core.ExperimentalGetImage + override fun analyze(imageProxy: ImageProxy) { + val mediaImage = imageProxy.image ?: return + + val rotationDegrees = imageProxy.imageInfo.rotationDegrees + + // We requested a setTargetAspectRatio, but it's not guaranteed that's what the camera + // stack is able to support, so we calculate the actual ratio from the first frame to + // know how to appropriately crop the image we want to analyze. + val imageHeight = mediaImage.height + val imageWidth = mediaImage.width + + val actualAspectRatio = imageWidth / imageHeight + + val convertImageToBitmap = ImageUtils.convertYuv420888ImageToBitmap(mediaImage) + val cropRect = Rect(0, 0, imageWidth, imageHeight) + + // If the image has a way wider aspect ratio than expected, crop less of the height so we + // don't end up cropping too much of the image. If the image has a way taller aspect ratio + // than expected, we don't have to make any changes to our cropping so we don't handle it + // here. + val currentCropPercentages = imageCropPercentages.value ?: return + if (actualAspectRatio > 3) { + val originalHeightCropPercentage = currentCropPercentages.first + val originalWidthCropPercentage = currentCropPercentages.second + imageCropPercentages.value = + Pair(originalHeightCropPercentage / 2, originalWidthCropPercentage) + } + + // If the image is rotated by 90 (or 270) degrees, swap height and width when calculating + // the crop. + val cropPercentages = imageCropPercentages.value ?: return + val heightCropPercent = cropPercentages.first + val widthCropPercent = cropPercentages.second + val (widthCrop, heightCrop) = when (rotationDegrees) { + 90, 270 -> Pair(heightCropPercent / 100f, widthCropPercent / 100f) + else -> Pair(widthCropPercent / 100f, heightCropPercent / 100f) + } + + cropRect.inset( + (imageWidth * widthCrop / 2).toInt(), + (imageHeight * heightCrop / 2).toInt() + ) + val croppedBitmap = + ImageUtils.rotateAndCrop(convertImageToBitmap, rotationDegrees, cropRect) + + // TODO call recognizeText() once implemented + } + + fun recognizeText() { + // TODO Use ML Kit's TextRecognition to analyze frames from the camera live feed. + } + + private fun getErrorMessage(exception: Exception): String? { + val mlKitException = exception as? MlKitException ?: return exception.message + return if (mlKitException.errorCode == MlKitException.UNAVAILABLE) { + "Waiting for text recognition model to be downloaded" + } else exception.message + } + + companion object { + private const val TAG = "TextAnalyzer" + } +} \ No newline at end of file diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/main/MainFragment.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/main/MainFragment.kt new file mode 100644 index 0000000..c7390ee --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/main/MainFragment.kt @@ -0,0 +1,366 @@ +/* + * Copyright 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.mlkit.codelab.translate.main + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.* +import android.os.Bundle +import android.util.DisplayMetrics +import android.util.Log +import android.view.LayoutInflater +import android.view.SurfaceHolder +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.camera.core.* +import androidx.camera.core.Camera +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import com.google.mlkit.codelab.translate.R +import com.google.mlkit.codelab.translate.analyzer.TextAnalyzer +import com.google.mlkit.codelab.translate.util.Language +import com.google.mlkit.codelab.translate.util.ScopedExecutor +import kotlinx.android.synthetic.main.main_fragment.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import kotlin.math.abs +import kotlin.math.ln +import kotlin.math.max +import kotlin.math.min + +class MainFragment : Fragment() { + + companion object { + fun newInstance() = MainFragment() + + // We only need to analyze the part of the image that has text, so we set crop percentages + // to avoid analyze the entire image from the live camera feed. + const val DESIRED_WIDTH_CROP_PERCENT = 8 + const val DESIRED_HEIGHT_CROP_PERCENT = 74 + + // This is an arbitrary number we are using to keep tab of the permission + // request. Where an app has multiple context for requesting permission, + // this can help differentiate the different contexts + private const val REQUEST_CODE_PERMISSIONS = 10 + + // This is an array of all the permission specified in the manifest + private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) + private const val RATIO_4_3_VALUE = 4.0 / 3.0 + private const val RATIO_16_9_VALUE = 16.0 / 9.0 + private const val TAG = "MainFragment" + } + + private var displayId: Int = -1 + private val viewModel: MainViewModel by viewModels() + private var cameraProvider: ProcessCameraProvider? = null + private var camera: Camera? = null + private var imageAnalyzer: ImageAnalysis? = null + private lateinit var container: ConstraintLayout + private lateinit var viewFinder: PreviewView + + /** Blocking camera operations are performed using this executor */ + private lateinit var cameraExecutor: ExecutorService + + private lateinit var scopedExecutor: ScopedExecutor + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.main_fragment, container, false) + } + + override fun onDestroyView() { + super.onDestroyView() + + // Shut down our background executor + cameraExecutor.shutdown() + scopedExecutor.shutdown() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + container = view as ConstraintLayout + viewFinder = container.findViewById(R.id.viewfinder) + + // Initialize our background executor + cameraExecutor = Executors.newSingleThreadExecutor() + scopedExecutor = ScopedExecutor(cameraExecutor) + + // Request camera permissions + if (allPermissionsGranted()) { + // Wait for the views to be properly laid out + viewFinder.post { + // Keep track of the display in which this view is attached + displayId = viewFinder.display.displayId + + // Set up the camera and its use cases + setUpCamera() + } + } else { + requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS) + } + + // Get available language list and set up the target language spinner + // with default selections. + val adapter = ArrayAdapter( + requireContext(), + android.R.layout.simple_spinner_dropdown_item, viewModel.availableLanguages + ) + + targetLangSelector.adapter = adapter + targetLangSelector.setSelection(adapter.getPosition(Language("en"))) + targetLangSelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>, + view: View?, + position: Int, + id: Long + ) { + viewModel.targetLang.value = adapter.getItem(position) + } + + override fun onNothingSelected(parent: AdapterView<*>) {} + } + + viewModel.sourceLang.observe(viewLifecycleOwner, Observer { srcLang.text = it.displayName }) + viewModel.translatedText.observe(viewLifecycleOwner, Observer { resultOrError -> + resultOrError?.let { + if (it.error != null) { + translatedText.error = resultOrError.error?.localizedMessage + } else { + translatedText.text = resultOrError.result + } + } + }) + viewModel.modelDownloading.observe(viewLifecycleOwner, Observer { isDownloading -> + progressBar.visibility = if (isDownloading) { + View.VISIBLE + } else { + View.INVISIBLE + } + progressText.visibility = progressBar.visibility + }) + + overlay.apply { + setZOrderOnTop(true) + holder.setFormat(PixelFormat.TRANSPARENT) + holder.addCallback(object : SurfaceHolder.Callback { + override fun surfaceChanged( + holder: SurfaceHolder?, + format: Int, + width: Int, + height: Int + ) { + } + + override fun surfaceDestroyed(holder: SurfaceHolder?) { + } + + override fun surfaceCreated(holder: SurfaceHolder?) { + holder?.let { drawOverlay(it, DESIRED_HEIGHT_CROP_PERCENT, DESIRED_WIDTH_CROP_PERCENT) } + } + + }) + } + } + + + /** Initialize CameraX, and prepare to bind the camera use cases */ + private fun setUpCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) + cameraProviderFuture.addListener(Runnable { + + // CameraProvider + cameraProvider = cameraProviderFuture.get() + + // Build and bind the camera use cases + bindCameraUseCases() + }, ContextCompat.getMainExecutor(requireContext())) + } + + private fun bindCameraUseCases() { + val cameraProvider = cameraProvider + ?: throw IllegalStateException("Camera initialization failed.") + + // Get screen metrics used to setup camera for full screen resolution + val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) } + Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}") + + val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels) + Log.d(TAG, "Preview aspect ratio: $screenAspectRatio") + + val rotation = viewFinder.display.rotation + + val preview = Preview.Builder() + .setTargetAspectRatio(screenAspectRatio) + .setTargetRotation(rotation) + .build() + + // Build the image analysis use case and instantiate our analyzer + imageAnalyzer = ImageAnalysis.Builder() + // We request aspect ratio but no resolution + .setTargetAspectRatio(screenAspectRatio) + .setTargetRotation(rotation) + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + .also { + it.setAnalyzer( + cameraExecutor + , TextAnalyzer( + requireContext(), + lifecycle, + viewModel.sourceText, + viewModel.imageCropPercentages + ) + ) + } + viewModel.sourceText.observe(viewLifecycleOwner, Observer { srcText.text = it }) + viewModel.imageCropPercentages.observe(viewLifecycleOwner, + Observer { drawOverlay(overlay.holder, it.first, it.second) }) + + // Select back camera since text detection does not work with front camera + val cameraSelector = + CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() + + try { + // Unbind use cases before rebinding + cameraProvider.unbindAll() + + // Bind use cases to camera + camera = cameraProvider.bindToLifecycle( + this, cameraSelector, preview, imageAnalyzer + ) + preview.setSurfaceProvider(viewFinder.surfaceProvider) + } catch (exc: IllegalStateException) { + Log.e(TAG, "Use case binding failed. This must be running on main thread.", exc) + } + } + + private fun drawOverlay( + holder: SurfaceHolder, + heightCropPercent: Int, + widthCropPercent: Int + ) { + val canvas = holder.lockCanvas() + val bgPaint = Paint().apply { + alpha = 140 + } + canvas.drawPaint(bgPaint) + val rectPaint = Paint() + rectPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) + rectPaint.style = Paint.Style.FILL + rectPaint.color = Color.WHITE + val outlinePaint = Paint() + outlinePaint.style = Paint.Style.STROKE + outlinePaint.color = Color.WHITE + outlinePaint.strokeWidth = 4f + val surfaceWidth = holder.surfaceFrame.width() + val surfaceHeight = holder.surfaceFrame.height() + + val cornerRadius = 25f + // Set rect centered in frame + val rectTop = surfaceHeight * heightCropPercent / 2 / 100f + val rectLeft = surfaceWidth * widthCropPercent / 2 / 100f + val rectRight = surfaceWidth * (1 - widthCropPercent / 2 / 100f) + val rectBottom = surfaceHeight * (1 - heightCropPercent / 2 / 100f) + val rect = RectF(rectLeft, rectTop, rectRight, rectBottom) + canvas.drawRoundRect( + rect, cornerRadius, cornerRadius, rectPaint + ) + canvas.drawRoundRect( + rect, cornerRadius, cornerRadius, outlinePaint + ) + val textPaint = Paint() + textPaint.color = Color.WHITE + textPaint.textSize = 50F + + val overlayText = getString(R.string.overlay_help) + val textBounds = Rect() + textPaint.getTextBounds(overlayText, 0, overlayText.length, textBounds) + val textX = (surfaceWidth - textBounds.width()) / 2f + val textY = rectBottom + textBounds.height() + 15f // put text below rect and 15f padding + canvas.drawText(getString(R.string.overlay_help), textX, textY, textPaint) + holder.unlockCanvasAndPost(canvas) + } + + /** + * [androidx.camera.core.ImageAnalysisConfig] requires enum value of + * [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9. + * + * Detecting the most suitable ratio for dimensions provided in @params by comparing absolute + * of preview ratio to one of the provided values. + * + * @param width - preview width + * @param height - preview height + * @return suitable aspect ratio + */ + private fun aspectRatio(width: Int, height: Int): Int { + val previewRatio = ln(max(width, height).toDouble() / min(width, height)) + if (abs(previewRatio - ln(RATIO_4_3_VALUE)) + <= abs(previewRatio - ln(RATIO_16_9_VALUE)) + ) { + return AspectRatio.RATIO_4_3 + } + return AspectRatio.RATIO_16_9 + } + + /** + * Process result from permission request dialog box, has the request + * been granted? If yes, start Camera. Otherwise display a toast + */ + override fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, grantResults: IntArray + ) { + if (requestCode == REQUEST_CODE_PERMISSIONS) { + if (allPermissionsGranted()) { + viewFinder.post { + // Keep track of the display in which this view is attached + displayId = viewFinder.display.displayId + + // Set up the camera and its use cases + setUpCamera() + } + } else { + Toast.makeText( + context, + "Permissions not granted by the user.", + Toast.LENGTH_SHORT + ).show() + } + } + } + + /** + * Check if all permission specified in the manifest have been granted + */ + private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission( + requireContext(), it + ) == PackageManager.PERMISSION_GRANTED + } +} diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/main/MainViewModel.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/main/MainViewModel.kt new file mode 100644 index 0000000..bf047c4 --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/main/MainViewModel.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.mlkit.codelab.translate.main + +import android.app.Application +import android.os.Handler +import android.util.LruCache +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import com.google.android.gms.tasks.OnCompleteListener +import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.Tasks +import com.google.mlkit.codelab.translate.util.Language +import com.google.mlkit.codelab.translate.util.ResultOrError +import com.google.mlkit.codelab.translate.util.SmoothedMutableLiveData +import com.google.mlkit.nl.languageid.LanguageIdentification +import com.google.mlkit.nl.translate.TranslateLanguage +import com.google.mlkit.nl.translate.Translation +import com.google.mlkit.nl.translate.Translator +import com.google.mlkit.nl.translate.TranslatorOptions +import com.google.mlkit.codelab.translate.main.MainFragment.Companion.DESIRED_HEIGHT_CROP_PERCENT +import com.google.mlkit.codelab.translate.main.MainFragment.Companion.DESIRED_WIDTH_CROP_PERCENT + +class MainViewModel(application: Application) : AndroidViewModel(application) { + + // TODO Instantiate LanguageIdentification + val targetLang = MutableLiveData() + val sourceText = SmoothedMutableLiveData(SMOOTHING_DURATION) + + // We set desired crop percentages to avoid having to analyze the whole image from the live + // camera feed. However, we are not guaranteed what aspect ratio we will get from the camera, so + // we use the first frame we get back from the camera to update these crop percentages based on + // the actual aspect ratio of images. + val imageCropPercentages = MutableLiveData>() + .apply { value = Pair(DESIRED_HEIGHT_CROP_PERCENT, DESIRED_WIDTH_CROP_PERCENT) } + val translatedText = MediatorLiveData() + private val translating = MutableLiveData() + val modelDownloading = SmoothedMutableLiveData(SMOOTHING_DURATION) + + private var modelDownloadTask: Task = Tasks.forCanceled() + + private val translators = + object : LruCache(NUM_TRANSLATORS) { + override fun create(options: TranslatorOptions): Translator { + return Translation.getClient(options) + } + + override fun entryRemoved( + evicted: Boolean, + key: TranslatorOptions, + oldValue: Translator, + newValue: Translator? + ) { + oldValue.close() + } + } + + val sourceLang = Transformations.switchMap(sourceText) { text -> + val result = MutableLiveData() + // TODO Call the language identification method and assigns the result if it is not + // undefined (“und”) + result + } + + override fun onCleared() { + // TODO Shut down ML Kit clients. + } + + private fun translate(): Task { + // TODO Take the source language value, target language value, and the source text and + // perform the translation. + // If the chosen target language model has not been downloaded to the device yet, + // call downloadModelIfNeeded() and then proceed with the translation. + return Tasks.forResult("") // replace this with your code + } + + // Gets a list of all available translation languages. + val availableLanguages: List = TranslateLanguage.getAllLanguages() + .map { Language(it) } + + init { + modelDownloading.setValue(false) + translating.value = false + // Create a translation result or error object. + val processTranslation = + OnCompleteListener { task -> + if (task.isSuccessful) { + translatedText.value = ResultOrError(task.result, null) + } else { + if (task.isCanceled) { + // Tasks are cancelled for reasons such as gating; ignore. + return@OnCompleteListener + } + translatedText.value = ResultOrError(null, task.exception) + } + } + // Start translation if any of the following change: detected text, source lang, target lang. + translatedText.addSource(sourceText) { translate().addOnCompleteListener(processTranslation) } + translatedText.addSource(sourceLang) { translate().addOnCompleteListener(processTranslation) } + translatedText.addSource(targetLang) { translate().addOnCompleteListener(processTranslation) } + } + + companion object { + // Amount of time (in milliseconds) to wait for detected text to settle + private const val SMOOTHING_DURATION = 50L + + private const val NUM_TRANSLATORS = 1 + } +} diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ImageUtils.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ImageUtils.kt new file mode 100644 index 0000000..1c28187 --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ImageUtils.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.mlkit.codelab.translate.util + +import android.graphics.Bitmap +import android.graphics.ImageFormat +import android.graphics.Matrix +import android.graphics.Rect +import android.media.Image +import androidx.annotation.ColorInt + +/** + * Utility class for manipulating images. + */ +object ImageUtils { + private val CHANNEL_RANGE = 0 until (1 shl 18) + + fun convertYuv420888ImageToBitmap(image: Image): Bitmap { + require(image.format == ImageFormat.YUV_420_888) { + "Unsupported image format $(image.format)" + } + + val planes = image.planes + + // Because of the variable row stride it's not possible to know in + // advance the actual necessary dimensions of the yuv planes. + val yuvBytes = planes.map { plane -> + val buffer = plane.buffer + val yuvBytes = ByteArray(buffer.capacity()) + buffer[yuvBytes] + buffer.rewind() // Be kind… + yuvBytes + } + + val yRowStride = planes[0].rowStride + val uvRowStride = planes[1].rowStride + val uvPixelStride = planes[1].pixelStride + val width = image.width + val height = image.height + @ColorInt val argb8888 = IntArray(width * height) + var i = 0 + for (y in 0 until height) { + val pY = yRowStride * y + val uvRowStart = uvRowStride * (y shr 1) + for (x in 0 until width) { + val uvOffset = (x shr 1) * uvPixelStride + argb8888[i++] = + yuvToRgb( + yuvBytes[0][pY + x].toIntUnsigned(), + yuvBytes[1][uvRowStart + uvOffset].toIntUnsigned(), + yuvBytes[2][uvRowStart + uvOffset].toIntUnsigned() + ) + } + } + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + bitmap.setPixels(argb8888, 0, width, 0, 0, width, height) + return bitmap + } + + fun rotateAndCrop( + bitmap: Bitmap, + imageRotationDegrees: Int, + cropRect: Rect + ): Bitmap { + val matrix = Matrix() + matrix.preRotate(imageRotationDegrees.toFloat()) + return Bitmap.createBitmap( + bitmap, + cropRect.left, + cropRect.top, + cropRect.width(), + cropRect.height(), + matrix, + true + ) + } + + @ColorInt + private fun yuvToRgb(nY: Int, nU: Int, nV: Int): Int { + var nY = nY + var nU = nU + var nV = nV + nY -= 16 + nU -= 128 + nV -= 128 + nY = nY.coerceAtLeast(0) + + // This is the floating point equivalent. We do the conversion in integer + // because some Android devices do not have floating point in hardware. + // nR = (int)(1.164 * nY + 2.018 * nU); + // nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU); + // nB = (int)(1.164 * nY + 1.596 * nV); + var nR = 1192 * nY + 1634 * nV + var nG = 1192 * nY - 833 * nV - 400 * nU + var nB = 1192 * nY + 2066 * nU + + // Clamp the values before normalizing them to 8 bits. + nR = nR.coerceIn(CHANNEL_RANGE) shr 10 and 0xff + nG = nG.coerceIn(CHANNEL_RANGE) shr 10 and 0xff + nB = nB.coerceIn(CHANNEL_RANGE) shr 10 and 0xff + return -0x1000000 or (nR shl 16) or (nG shl 8) or nB + } +} + +private fun Byte.toIntUnsigned(): Int { + return toInt() and 0xFF +} diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/Language.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/Language.kt new file mode 100644 index 0000000..628c573 --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/Language.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.mlkit.codelab.translate.util + +import java.util.* + +/** + * Holds the language code (i.e. "en") and the corresponding localized full language name + * (i.e. "English") + */ +class Language(val code: String) : Comparable { + + val displayName: String + get() = Locale(code).displayName + + override fun equals(other: Any?): Boolean { + if (other === this) { + return true + } + + if (other !is Language) { + return false + } + + val otherLang = other as Language? + return otherLang!!.code == code + } + + override fun toString(): String { + return displayName + } + + override fun compareTo(other: Language): Int { + return this.displayName.compareTo(other.displayName) + } + + override fun hashCode(): Int { + return code.hashCode() + } +} diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ResultOrError.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ResultOrError.kt new file mode 100644 index 0000000..553acc1 --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ResultOrError.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.mlkit.codelab.translate.util + +/** + * Holds a result or some operation or the exception. + */ +class ResultOrError(var result: String?, var error: Exception?) diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ScopedExecutor.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ScopedExecutor.kt new file mode 100644 index 0000000..f514858 --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/ScopedExecutor.kt @@ -0,0 +1,19 @@ +package com.google.mlkit.codelab.translate.util + +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicBoolean + +class ScopedExecutor(private val executor: Executor) : Executor { + + private val isShutdown = AtomicBoolean() + + fun shutdown() { + isShutdown.set(true) + } + + override fun execute(command: Runnable) { + executor.execute { + if (!isShutdown.get()) command.run() + } + } +} \ No newline at end of file diff --git a/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/SmoothedMutableLiveData.kt b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/SmoothedMutableLiveData.kt new file mode 100644 index 0000000..f7179bf --- /dev/null +++ b/translate/final/app/src/main/java/com/google/mlkit/codelab/translate/util/SmoothedMutableLiveData.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.google.mlkit.codelab.translate.util + +import android.os.Handler +import androidx.lifecycle.MutableLiveData + +/** + * A {@link MutableLiveData} that only emits change events when the underlying data has been stable + * for the configured amount of time. + * + * @param duration time delay to wait in milliseconds + */ +class SmoothedMutableLiveData(private val duration: Long) : MutableLiveData() { + private var pendingValue: T? = null + private val runnable = Runnable { + super.setValue(pendingValue) + } + + override fun setValue(value: T) { + if (value != pendingValue) { + pendingValue = value + Handler().removeCallbacks(runnable) + Handler().postDelayed(runnable, duration) + } + } +} diff --git a/translate/final/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/translate/final/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..93b7d80 --- /dev/null +++ b/translate/final/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + diff --git a/translate/final/app/src/main/res/drawable/greyscale_regular_3x.png b/translate/final/app/src/main/res/drawable/greyscale_regular_3x.png new file mode 100644 index 0000000..1a1f571 Binary files /dev/null and b/translate/final/app/src/main/res/drawable/greyscale_regular_3x.png differ diff --git a/translate/final/app/src/main/res/drawable/ic_launcher_background.xml b/translate/final/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..5408240 --- /dev/null +++ b/translate/final/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/translate/final/app/src/main/res/font/pt_sans.ttf b/translate/final/app/src/main/res/font/pt_sans.ttf new file mode 100644 index 0000000..83a21b7 Binary files /dev/null and b/translate/final/app/src/main/res/font/pt_sans.ttf differ diff --git a/translate/final/app/src/main/res/layout/main_activity.xml b/translate/final/app/src/main/res/layout/main_activity.xml new file mode 100644 index 0000000..127c258 --- /dev/null +++ b/translate/final/app/src/main/res/layout/main_activity.xml @@ -0,0 +1,24 @@ + + + + diff --git a/translate/final/app/src/main/res/layout/main_fragment.xml b/translate/final/app/src/main/res/layout/main_fragment.xml new file mode 100644 index 0000000..c266ae3 --- /dev/null +++ b/translate/final/app/src/main/res/layout/main_fragment.xml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/translate/final/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/translate/final/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2bef0f7 --- /dev/null +++ b/translate/final/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/translate/final/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/translate/final/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..2bef0f7 --- /dev/null +++ b/translate/final/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/translate/final/app/src/main/res/mipmap-hdpi/ic_launcher.png b/translate/final/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..898f3ed Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/translate/final/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/translate/final/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..dffca36 Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/translate/final/app/src/main/res/mipmap-mdpi/ic_launcher.png b/translate/final/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..64ba76f Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/translate/final/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/translate/final/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..dae5e08 Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/translate/final/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/translate/final/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..e5ed465 Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/translate/final/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/translate/final/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..14ed0af Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/translate/final/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/translate/final/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..b0907ca Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/translate/final/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/translate/final/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d8ae031 Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/translate/final/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/translate/final/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..2c18de9 Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/translate/final/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/translate/final/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..beed3cd Binary files /dev/null and b/translate/final/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/translate/final/app/src/main/res/values/colors.xml b/translate/final/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..1cf589b --- /dev/null +++ b/translate/final/app/src/main/res/values/colors.xml @@ -0,0 +1,23 @@ + + + + + #008577 + #00574B + #D81B60 + diff --git a/translate/final/app/src/main/res/values/strings.xml b/translate/final/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..1cb9e22 --- /dev/null +++ b/translate/final/app/src/main/res/values/strings.xml @@ -0,0 +1,24 @@ + + + + ML Kit Translate Codelab + Unknown error occurred. + Google Translate attribution + Center text in box + Downloading model files... + diff --git a/translate/final/app/src/main/res/values/styles.xml b/translate/final/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..fbd2516 --- /dev/null +++ b/translate/final/app/src/main/res/values/styles.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + diff --git a/translate/final/build.gradle b/translate/final/build.gradle new file mode 100644 index 0000000..a7b59eb --- /dev/null +++ b/translate/final/build.gradle @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/translate/final/gradle.properties b/translate/final/gradle.properties new file mode 100644 index 0000000..b5e9f60 --- /dev/null +++ b/translate/final/gradle.properties @@ -0,0 +1,38 @@ +# +# Copyright 2019 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/translate/final/gradle/wrapper/gradle-wrapper.jar b/translate/final/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/translate/final/gradle/wrapper/gradle-wrapper.jar differ diff --git a/translate/final/gradle/wrapper/gradle-wrapper.properties b/translate/final/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..978d878 --- /dev/null +++ b/translate/final/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,23 @@ +# +# Copyright 2019 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +#Wed Sep 18 03:11:32 EDT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/translate/final/gradlew b/translate/final/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/translate/final/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/translate/final/gradlew.bat b/translate/final/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/translate/final/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/translate/final/settings.gradle b/translate/final/settings.gradle new file mode 100644 index 0000000..8178fd4 --- /dev/null +++ b/translate/final/settings.gradle @@ -0,0 +1,19 @@ +/* + * Copyright 2019 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +include ':app' +rootProject.name='ML Kit Translate Codelab'