Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Google IO recap

Google IO recap

Android Academy Google IO recap

Avatar for Gil Goldzweig

Gil Goldzweig

June 26, 2019
Tweet

More Decks by Gil Goldzweig

Other Decks in Programming

Transcript

  1. Jetpack security - File val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val encryptedFile

    = EncryptedFile.Builder( File(context.getFilesDir(), "/your/file"), ).build()
  2. Jetpack security - File val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val encryptedFile

    = EncryptedFile.Builder( File(context.getFilesDir(), "/your/file"), context, ).build()
  3. Jetpack security - File val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val encryptedFile

    = EncryptedFile.Builder( File(context.getFilesDir(), "/your/file"), context, masterKey, ).build()
  4. Jetpack security - File val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val encryptedFile

    = EncryptedFile.Builder( File(context.getFilesDir(), "/your/file"), context, masterKey, EncryptedFileKeyset.FileEncryptionScheme.AES256_GCM_HKDF_4KB ).build()
  5. Jetpack security - Read File val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val

    encryptedFile = EncryptedFile.Builder( File(context.getFilesDir(), "/your/file"), context, masterKey, EncryptedFileKeyset.FileEncryptionScheme.AES256_GCM_HKDF_4KB ).build() encryptedFile.openInputStream()
  6. Jetpack security - Write File val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val

    encryptedFile = EncryptedFile.Builder( File(context.getFilesDir(), "/your/file"), context, masterKey, EncryptedFileKeyset.FileEncryptionScheme.AES256_GCM_HKDF_4KB ).build() encryptedFile.openInputStrem() encryptedFile.openOutputStream()
  7. Jetpack security - SharedPreferences val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val sharedPreferences

    = EncryptedSharedPreferences.create( "shareadpreferencesName", masterKey, context, )
  8. Jetpack security - SharedPreferences val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val sharedPreferences

    = EncryptedSharedPreferences.create( "shareadpreferencesName", masterKey, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM )
  9. Jetpack security • Currently in alpha • Min api 23

    • More information available here
  10. Benchmark • Makes it easy to benchmark your code •

    Handles warmup and clock stability for you • Gives you a accurate result • Runs as instrumented test
  11. Benchmark @RunWith(AndroidJUnit4::class) class MyBenchmark() { @get:Rule val rule = BenchmarkRule()

    @Test fun simpleViewInflate() { val context = ApplicationProvider.getApplicationContext() val inflater = LayoutInflater.from(context) val root = FrameLayout(context) } }
  12. Benchmark @RunWith(AndroidJUnit4::class) class MyBenchmark() { @get:Rule val rule = BenchmarkRule()

    @Test fun simpleViewInflate() { val context = ApplicationProvider.getApplicationContext() val inflater = LayoutInflater.from(context) val root = FrameLayout(context) rule.measureRepeated { inflater.inflate(R.layout.test_simple_view, root, false) } } }
  13. Benchmark @RunWith(AndroidJUnit4::class) class MyBanchmark() { @get:Rule val rule = BanchmarkRule()

    @Test fun simpleViewInflate() { val context = ApplicationProvider.getApplicationContext() val inflater = LayoutInflater.from(context) val root = FrameLayout(context) rule.measureRepeated { inflater.inflate(R.layout.test_simple_view, root, false) } } }
  14. Benchmark • Available as part of AndroidX • Currently in

    alpha • You can learn more about Benchmark here
  15. Jetpack compose UI is defined as a function • Take

    data as input • Emits UI hierarchy when invoked @Composable fun Greeting(name: String) { Text("Hello: $name") }
  16. What is compose? • A set of jetpack UI widgets

    • A Kotlin compiler plugin • Unbundled from platform release • Uses canvas instead of Views • Fully compatible with your existing app/code
  17. Jetpack compose class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState:

    Bundle) { super.onCreate(savedInstanceState, persistentState) setContent { Greeting("Android academy") } } }
  18. @Composable fun Greeting(vararg names: String) { List { for (name

    in names) { Text("Hello: $name") } } } Jetpack compose - List
  19. Jetpack compose @Composable fun Counter() { val amount = +state

    { 0 } Column { Text("Counter demo") } }
  20. Jetpack compose @Composable fun Counter() { val amount = +state

    { 0 } Column { Text("Counter demo") Button(text = "Add", onClick = { amount.value++ }) Button(text = "Subtract", onClick = { amount.value-- }) } }
  21. Jetpack compose @Composable fun Counter() { val amount = +state

    { 0 } Column { Text("Counter demo") Button(text = "Add", onClick = { amount.value++ }) Button(text = "Subtract", onClick = { amount.value-- }) Text("Clicks: ${amount.value}") } }
  22. Jetpack compose • Currently in pre alpha (Very early stage)

    • Available under AOSP and can be used here • Join the slack channel
  23. Easy…. private val captureCallback = object : CameraCaptureSession.CaptureCallback() { private

    fun process(result: CaptureResult) { when (state) { STATE_PREVIEW -> Unit // Do nothing when the camera preview is working normally. STATE_WAITING_LOCK -> capturePicture(result) STATE_WAITING_PRECAPTURE -> { // CONTROL_AE_STATE can be null on some devices val aeState = result.get(CaptureResult.CONTROL_AE_STATE) if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { state = STATE_WAITING_NON_PRECAPTURE } } STATE_WAITING_NON_PRECAPTURE -> { // CONTROL_AE_STATE can be null on some devices val aeState = result.get(CaptureResult.CONTROL_AE_STATE) if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { state = STATE_PICTURE_TAKEN captureStillPicture() } } } } private fun capturePicture(result: CaptureResult) { val afState = result.get(CaptureResult.CONTROL_AF_STATE) if (afState == null) { captureStillPicture() } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { // CONTROL_AE_STATE can be null on some devices val aeState = result.get(CaptureResult.CONTROL_AE_STATE) if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { state = STATE_PICTURE_TAKEN captureStillPicture() } else { runPrecaptureSequence() } } } override fun onCaptureProgressed(session: CameraCaptureSession, request: CaptureRequest, partialResult: CaptureResult) { process(partialResult) } override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { process(result) } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.fragment_camera2_basic, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { view.findViewById<View>(R.id.picture).setOnClickListener(this) view.findViewById<View>(R.id.info).setOnClickListener(this) textureView = view.findViewById(R.id.texture) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) file = File(activity.getExternalFilesDir(null), PIC_FILE_NAME) } nsor coordinate * @param maxWidth The maximum width that can be chosen * @param maxHeight The maximum height that can be chosen * @param aspectRatio The aspect ratio * @return The optimal `Size`, or an arbitrary one if none were big enough */ @JvmStatic private fun chooseOptimalSize( choices: Array<Size>, textureViewWidth: Int, textureViewHeight: Int, maxWidth: Int, maxHeight: Int, aspectRatio: Size ): Size { // Collect the supported resolutions that are at least as big as the preview Surface val bigEnough = ArrayList<Size>() // Collect the supported resolutions that are smaller than the preview Surface val notBigEnough = ArrayList<Size>() val w = aspectRatio.width val h = aspectRatio.height for (option in choices) { if (option.width <= maxWidth && option.height <= maxHeight && option.height == option.width * h / w) { if (option.width >= textureViewWidth && option.height >= textureViewHeight) { bigEnough.add(option) } else { notBigEnough.add(option) } } } // Pick the smallest of those big enough. If there is no one big enough, pick the // largest of those not big enough. if (bigEnough.size > 0) { return Collections.min(bigEnough, CompareSizesByArea()) } else if (notBigEnough.size > 0) { return Collections.max(notBigEnough, CompareSizesByArea()) } else { Log.e(TAG, "Couldn't find any suitable preview size") return choices[0] } } @JvmStatic fun newInstance(): Camera2BasicFragment = Camera2BasicFragment() } } override fun onResume() { super.onResume() startBackgroundThread() // When the screen is turned off and turned back on, the SurfaceTexture is already // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open // a camera and start preview from here (otherwise, we wait until the surface is ready in // the SurfaceTextureListener). if (textureView.isAvailable) { openCamera(textureView.width, textureView.height) } else { textureView.surfaceTextureListener = surfaceTextureListener } } override fun onPause() { closeCamera() stopBackgroundThread() super.onPause() } private fun requestCameraPermission() { if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { ConfirmationDialog().show(childFragmentManager, FRAGMENT_DIALOG) } else { requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { if (requestCode == REQUEST_CAMERA_PERMISSION) { if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { ErrorDialog.newInstance(getString(R.string.request_permission)) .show(childFragmentManager, FRAGMENT_DIALOG) } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults) } } /** * Sets up member variables related to camera. * * @param width The width of available size for camera preview * @param height The height of available size for camera preview */ private fun setUpCameraOutputs(width: Int, height: Int) { val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager try { for (cameraId in manager.cameraIdList) { val characteristics = manager.getCameraCharacteristics(cameraId) // We don't use a front facing camera in this sample. val cameraDirection = characteristics.get(CameraCharacteristics.LENS_FACING) if (cameraDirection != null && cameraDirection == CameraCharacteristics.LENS_FACING_FRONT) { continue } val map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue // For still image captures, we use the largest available size. val largest = Collections.max( Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)), CompareSizesByArea()) imageReader = ImageReader.newInstance(largest.width, largest.height, ImageFormat.JPEG, /*maxImages*/ 2).apply { setOnImageAvailableListener(onImageAvailableListener, backgroundHandler) } // Find out if we need to swap dimension to get the preview size relative to sensor // coordinate. val displayRotation = activity.windowManager.defaultDisplay.rotation sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) val swappedDimensions = areDimensionsSwapped(displayRotation) val displaySize = Point() activity.windowManager.defaultDisplay.getSize(displaySize) val rotatedPreviewWidth = if (swappedDimensions) height else width val rotatedPreviewHeight = if (swappedDimensions) width else height var maxPreviewWidth = if (swappedDimensions) displaySize.y else displaySize.x var maxPreviewHeight = if (swappedDimensions) displaySize.x else displaySize.y if (maxPreviewWidth > MAX_PREVIEW_WIDTH) maxPreviewWidth = MAX_PREVIEW_WIDTH if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) maxPreviewHeight = MAX_PREVIEW_HEIGHT // Danger, W.R.! Attempting to use too large a preview size could exceed the camera // bus' bandwidth limitation, resulting in gorgeous previews but the storage of // garbage capture data. previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java), rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, maxPreviewHeight, largest) // We fit the aspect ratio of TextureView to the size of preview we picked. if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { textureView.setAspectRatio(previewSize.width, previewSize.height) } else { textureView.setAspectRatio(previewSize.height, previewSize.width) } // Check if the flash is supported. flashSupported = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true val captureBuilder = cameraDevice?.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply { addTarget(imageReader?.surface) // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) // We have to take that into account and rotate JPEG properly. // For devices with orientation of 90, we return our mapping from ORIENTATIONS. // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. set(CaptureRequest.JPEG_ORIENTATION, (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360) // Use the same AE and AF modes as the preview. set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) }?.also { setAutoFlash(it) } val captureCallback = object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { activity.showToast("Saved: $file") Log.d(TAG, file.toString()) unlockFocus() } } captureSession?.apply { stopRepeating() abortCaptures() capture(captureBuilder?.build(), captureCallback, null) } } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } } /** * Unlock the focus. This method should be called when still image capture sequence is * finished. */ private fun unlockFocus() { try { // Reset the auto-focus trigger previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL) setAutoFlash(previewRequestBuilder) captureSession?.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler) // After this, the camera will go back to the normal state of preview. state = STATE_PREVIEW captureSession?.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler) } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } } override fun onClick(view: View) { when (view.id) { R.id.picture -> lockFocus() R.id.info -> { if (activity != null) { AlertDialog.Builder(activity) .setMessage(R.string.intro_message) .setPositiveButton(android.R.string.ok, null) .show() } } } } private fun setAutoFlash(requestBuilder: CaptureRequest.Builder) { if (flashSupported) { requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) } } companion object { /** * Conversion from screen rotation to JPEG orientation. */ private val ORIENTATIONS = SparseIntArray() private val FRAGMENT_DIALOG = "dialog" init { ORIENTATIONS.append(Surface.ROTATION_0, 90) ORIENTATIONS.append(Surface.ROTATION_90, 0) ORIENTATIONS.append(Surface.ROTATION_180, 270) ORIENTATIONS.append(Surface.ROTATION_270, 180) } /** * Tag for the [Log]. */ private val TAG = "Camera2BasicFragment" /** * Camera state: Showing camera preview. */ private val STATE_PREVIEW = 0 /** * Camera state: Waiting for the focus to be locked. */ private val STATE_WAITING_LOCK = 1 /** * Camera state: Waiting for the exposure to be precapture state. */ private val STATE_WAITING_PRECAPTURE = 2 /** * Camera state: Waiting for the exposure state to be something other than precapture. */ private val STATE_WAITING_NON_PRECAPTURE = 3 /** * Camera state: Picture was taken. */ private val STATE_PICTURE_TAKEN = 4 /** * Max preview width that is guaranteed by Camera2 API */ private val MAX_PREVIEW_WIDTH = 1920 /** * Max preview height that is guaranteed by Camera2 API */ private val MAX_PREVIEW_HEIGHT = 1080 /** * Given `choices` of `Size`s supported by a camera, choose the smallest one that * is at least as large as the respective texture view size, and that is at most as large as * the respective max size, and whose aspect ratio matches with the specified value. If such * size doesn't exist, choose the largest one that is at most as large as the respective max * size, and whose aspect ratio matches with the specified value. * * @param choices The list of sizes that the camera supports for the intended * output class * @param textureViewWidth The width of the texture view relative to sensor coordinate this.cameraId = cameraId // We've found a viable camera and finished setting up member variables, // so we don't need to iterate through other available cameras. return } } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } catch (e: NullPointerException) { // Currently an NPE is thrown when the Camera2API is used but not supported on the // device this code runs. ErrorDialog.newInstance(getString(R.string.camera_error)) .show(childFragmentManager, FRAGMENT_DIALOG) } } /** * Determines if the dimensions are swapped given the phone's current rotation. * * @param displayRotation The current rotation of the display * * @return true if the dimensions are swapped, false otherwise. */ private fun areDimensionsSwapped(displayRotation: Int): Boolean { var swappedDimensions = false when (displayRotation) { Surface.ROTATION_0, Surface.ROTATION_180 -> { if (sensorOrientation == 90 || sensorOrientation == 270) { swappedDimensions = true } } Surface.ROTATION_90, Surface.ROTATION_270 -> { if (sensorOrientation == 0 || sensorOrientation == 180) { swappedDimensions = true } } else -> { Log.e(TAG, "Display rotation is invalid: $displayRotation") } } return swappedDimensions } /** * Opens the camera specified by [Camera2BasicFragment.cameraId]. */ private fun openCamera(width: Int, height: Int) { val permission = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) if (permission != PackageManager.PERMISSION_GRANTED) { requestCameraPermission() return } setUpCameraOutputs(width, height) configureTransform(width, height) val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager try { // Wait for camera to open - 2.5 seconds is sufficient if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { throw RuntimeException("Time out waiting to lock camera opening.") } manager.openCamera(cameraId, stateCallback, backgroundHandler) } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } catch (e: InterruptedException) { throw RuntimeException("Interrupted while trying to lock camera opening.", e) } } /** * Closes the current [CameraDevice]. */ private fun closeCamera() { try { cameraOpenCloseLock.acquire() captureSession?.close() captureSession = null cameraDevice?.close() cameraDevice = null imageReader?.close() imageReader = null } catch (e: InterruptedException) { throw RuntimeException("Interrupted while trying to lock camera closing.", e) } finally { cameraOpenCloseLock.release() } } /** * Starts a background thread and its [Handler]. */ private fun startBackgroundThread() { backgroundThread = HandlerThread("CameraBackground").also { it.start() } backgroundHandler = Handler(backgroundThread?.looper) } /** * Stops the background thread and its [Handler]. hen we get a response in * [.captureCallback] from both [.lockFocus]. */ private fun captureStillPicture() { try { if (activity == null || cameraDevice == null) return val rotation = activity.windowManager.defaultDisplay.rotation // This is the CaptureRequest.Builder that we use to take a picture. e
  24. CameraX • Simplified usage • Solve many bugs and pain

    points • Consistency across devices
  25. Preview val previewConfig = PreviewConfig.Builder().build() val preview = Preview(previewConfig) preview.setOnPreviewOutputUpdateListener

    { previewOutput: Preview.PreviewOutput? -> /* Your code here. * For example, use previewOutput?.getSurfaceTexture() * and post to a GL renderer. */ }
  26. Preview val previewConfig = PreviewConfig.Builder().build() val preview = Preview(previewConfig) preview.setOnPreviewOutputUpdateListener

    { previewOutput: Preview.PreviewOutput? -> /* Your code here. * For example, use previewOutput?.getSurfaceTexture() * and post to a GL renderer. */ } CameraX.bindToLifecycle(this as LifecycleOwner, preview)
  27. Preview val previewConfig = PreviewConfig.Builder().build() val preview = Preview(previewConfig) preview.setOnPreviewOutputUpdateListener

    { previewOutput: Preview.PreviewOutput? -> /* Your code here. * For example, use previewOutput?.getSurfaceTexture() * and post to a GL renderer. */ } CameraX.bindToLifecycle(this as LifecycleOwner, preview)
  28. Image analysis val imageAnalysisConfig = ImageAnalysisConfig.Builder() .setTargetResolution(Size(1280, 720)) .build() val

    imageAnalysis = ImageAnalysis(imageAnalysisConfig) imageAnalysis.setAnalyzer { image: ImageProxy, rotationDegrees: Int -> //insert your analysis code here }
  29. Image analysis val imageAnalysisConfig = ImageAnalysisConfig.Builder() .setTargetResolution(Size(1280, 720)) .build() val

    imageAnalysis = ImageAnalysis(imageAnalysisConfig) imageAnalysis.setAnalyzer { image: ImageProxy, rotationDegrees: Int -> //insert your analysis code here } CameraX.bindToLifecycle(this as LifecycleOwner, imageAnalysis, preview)
  30. Image analysis val imageAnalysisConfig = ImageAnalysisConfig.Builder() .setTargetResolution(Size(1280, 720)) .build() val

    imageAnalysis = ImageAnalysis(imageAnalysisConfig) imageAnalysis.setAnalyzer { image: ImageProxy, rotationDegrees: Int -> //insert your analysis code here } CameraX.bindToLifecycle(this as LifecycleOwner, imageAnalysis, preview)
  31. Image capture val imageCaptureConfig = ImageCaptureConfig.Builder() .setTargetRotation(windowManager.defaultDisplay.rotation) .build() val imageCapture

    = ImageCapture(imageCaptureConfig) CameraX.bindToLifecycle(this as LifecycleOwner, imageCapture, imageAnalysis, preview)
  32. Image capture val imageCaptureConfig = ImageCaptureConfig.Builder() .setTargetRotation(windowManager.defaultDisplay.rotation) .build() val imageCapture

    = ImageCapture(imageCaptureConfig) CameraX.bindToLifecycle(this as LifecycleOwner, imageCapture, imageAnalysis, preview)
  33. fun onClick() { val file = File(...) imageCapture.takePicture(file, object :

    ImageCapture.OnImageSavedListener { override fun onError(error: ImageCapture.UseCaseError, message: String, exc: Throwable?) { // insert your code here. } override fun onImageSaved(file: File) { // insert your code here. } }) } Listen to save result
  34. fun onClick() { val file = File(...) imageCapture.takePicture(file, object :

    ImageCapture.OnImageSavedListener { override fun onError(error: ImageCapture.UseCaseError, message: String, exc: Throwable?) { // insert your code here. } override fun onImageSaved(file: File) { // insert your code here. } }) } Save an Image on user action
  35. Extensions val builder = ImageCaptureConfig.Builder() val bokehImageCapture = BokehImageCaptureExtender.create(builder) if

    (bokehImageCapture.isExtensionAvailable()) { bokehImageCapture.enableExtension() }