Slide 1

Slide 1 text

@_saulmm for modern Android Development Hidden pearls and wats

Slide 2

Slide 2 text

Vigo, Galicia Saúl Molinero Saúl Molinero @_saulmm

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

@_saulmm

Slide 5

Slide 5 text

@_saulmm TODO

Slide 6

Slide 6 text

@_saulmm android.R.string

Slide 7

Slide 7 text

@_saulmm android.R.string

Slide 8

Slide 8 text

@_saulmm android.R.string

Slide 9

Slide 9 text

@_saulmm %1$d selected items Not happy with ? support: [email protected] Repunantiño Repunantiño

Slide 10

Slide 10 text

@_saulmm XML Entities %1$d selected items Not happy with ? support: ]> &app_name; &support_mail; &app_name;

Slide 11

Slide 11 text

@_saulmm

Slide 12

Slide 12 text

@_saulmm Sorry. You don\'t have notifications yet.\n Tap the megaphone icon to subscribe! binding.txtSubscribe.setText( R.string.msg_subscribe )

Slide 13

Slide 13 text

@_saulmm

Slide 14

Slide 14 text

@_saulmm Sorry. You don\'t have notifications yet.\n Tap the megaphone icon to subscribe! Span annotations https://www.youtube.com/watch?v=x-FcOX6ErdI

Slide 15

Slide 15 text

@_saulmm spans?.forEach { annotation -> if (annotation.key == "foreground") { val color = annotation.getResId(this) if (color != 0) { ssb.setSpan( ForegroundColorSpan(ContextCompat.getColor(this, color)), text.getSpanStart(annotation), text.getSpanEnd(annotation), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) } } } Span annotations val text = getText(R.string.msg_subscribe) as SpannedString val ssb = SpannableStringBuilder(text) val spans = text.getSpans(0, text.length, Annotation::class.java) https://www.youtube.com/watch?v=x-FcOX6ErdI

Slide 16

Slide 16 text

@_saulmm Span annotations

Slide 17

Slide 17 text

@_saulmm Span annotations Sorry, You don\'t have notifications yet.\n Tap the ( ) icon to subscribe! https://www.youtube.com/watch?v=x-FcOX6ErdI

Slide 18

Slide 18 text

@_saulmm spans?.forEach { annotation -> } // Other annotations... if (annotation.key == "src") { val id = annotation.getResId(this) if (id != 0) { ssb.setSpan( ImageSpan(this, id, ImageSpan.ALIGN_BASELINE), text.getSpanStart(annotation), text.getSpanEnd(annotation), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) } } Span annotations https://www.youtube.com/watch?v=x-FcOX6ErdI

Slide 19

Slide 19 text

@_saulmm spans?.forEach { annotation -> } // Other annotations... if (annotation.key == "src") { val id = annotation.getResId(this) if (id != 0) { ssb.setSpan( ImageSpan(this, id, ImageSpan.ALIGN_BASELINE), text.getSpanStart(annotation), text.getSpanEnd(annotation), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) } } Span annotations https://www.youtube.com/watch?v=x-FcOX6ErdI

Slide 20

Slide 20 text

@_saulmm Go to source

Slide 21

Slide 21 text

@_saulmm Go to source .([filename].java:[line]) ([filename].kt:[line])

Slide 22

Slide 22 text

@_saulmm Go to source Log.i("IMPORTANT", "look at my java file .(Hello.java:6)") Log.i("IMPORTANT", "For kotlin too! (MainActivity.kt:20)") .([filename].java:[line]) ([filename].kt:[line])

Slide 23

Slide 23 text

@_saulmm Go to source Log.i("IMPORTANT", "look at my java file .(Hello.java:6)") Log.i("IMPORTANT", "For kotlin too! (MainActivity.kt:20)") .([filename].java:[line]) ([filename].kt:[line])

Slide 24

Slide 24 text

@_saulmm Log.i(TAG, "An information log") Log.d(TAG, "A debug log") Log.v(TAG, "A verbose log") Log.e(TAG, "An error log") Log.w(TAG, "A Warning log")

Slide 25

Slide 25 text

@_saulmm Log.i(TAG, "An information log") Log.d(TAG, "A debug log") Log.v(TAG, "A verbose log") Log.e(TAG, "An error log") Log.w(TAG, "A Warning log") 2019-12-12 10:55:55.165 I/yea: An information log 2019-12-12 10:55:55.165 D/yea debug log 2019-12-12 10:55:55.165 V/yea: A verbose log 2019-12-12 10:55:55.165 E/yea: An error log 2019-12-12 10:55:55.165 W/yea: A warning log

Slide 26

Slide 26 text

@_saulmm Log.wtf // What a terrible failure Log.wtf(TAG, “An ultra not expected thing happened") https://developer.android.com/reference/android/util/Log.html#wtf(java.lang.String,%20java.lang.String)

Slide 27

Slide 27 text

@_saulmm Log /** * What a Terrible Failure: Report a condition that should never happen. * The error will always be logged at level ASSERT with the call stack. * Depending on system configuration, a report may be added to the * {@link android.os.DropBoxManager} and/or the process may be terminated * immediately with an error dialog. * @param tag Used to identify the source of a log message. * @param msg The message you would like logged. */ public static int wtf(String tag, String msg) { return wtf(LOG_ID_MAIN, tag, msg, null, false, false); } https://developer.android.com/reference/android/util/Log.html#wtf(java.lang.String,%20java.lang.String)

Slide 28

Slide 28 text

@_saulmm Log /** * What a Terrible Failure: Report a condition that should never happen. * The error will always be logged at level ASSERT with the call stack. * Depending on system configuration, a report may be added to the * {@link android.os.DropBoxManager} and/or the process may be terminated * immediately with an error dialog. * @param tag Used to identify the source of a log message. * @param msg The message you would like logged. */ public static int wtf(String tag, String msg) { return wtf(LOG_ID_MAIN, tag, msg, null, false, false); } https://developer.android.com/reference/android/util/Log.html#wtf(java.lang.String,%20java.lang.String)

Slide 29

Slide 29 text

@_saulmm Log Log.e(TAG, "") != Log.wtf(TAG, "") Might crash your app https://developer.android.com/reference/android/util/Log.html#wtf(java.lang.String,%20java.lang.String)

Slide 30

Slide 30 text

@_saulmm @+id/

Slide 31

Slide 31 text

@_saulmm @+id/ Without an id, content clears with a configuration change

Slide 32

Slide 32 text

@_saulmm @+id/

Slide 33

Slide 33 text

@_saulmm beginDelayedTransition binding.containerTitle.setOnClickListener { binding.txtContent.text = if (expanded) aLongText() else "" binding.txtContent.isVisible = expanded expanded = !expanded } TransitionManager.beginDelayedTransition( binding.root as ViewGroup) https://developer.android.com/reference/android/transition/TransitionManager#beginDelayedTransition(android.view.ViewGroup)

Slide 34

Slide 34 text

@_saulmm beginDelayedTransition() package android.transition; /** * Convenience method to animate, using the default transition, * to a new scene defined by all changes within the given scene root between * calling this method and the next rendering frame. */ public static void beginDelayedTransition(final ViewGroup sceneRoot) { beginDelayedTransition(sceneRoot, null); } https://developer.android.com/reference/android/transition/TransitionManager#beginDelayedTransition(android.view.ViewGroup)

Slide 35

Slide 35 text

@_saulmm animateLayoutChanges private fun onAddItemClick() { DataBindingUtil.inflate( layoutInflater, R.layout.item_expandable, binding.containerExpandables, true ) <.../>

Slide 36

Slide 36 text

@_saulmm animateLayoutChanges private fun onAddItemClick() { DataBindingUtil.inflate( layoutInflater, R.layout.item_expandable, binding.containerExpandables, true ) <.../>

Slide 37

Slide 37 text

@_saulmm animateLayoutChanges <.../> binding.containerFaq.layoutTransition .enableTransitionType(LayoutTransition.CHANGING)

Slide 38

Slide 38 text

@_saulmm getDrawable

Slide 39

Slide 39 text

@_saulmm getDrawable ContextCompat.getDrawable( @NonNull Context context, @DrawableRes int resId )

Slide 40

Slide 40 text

@_saulmm getDrawable ContextCompat.getDrawable( @NonNull Context context, @DrawableRes int resId ) ResourcesCompat.getDrawable( @NonNull Resources res, @DrawableRes int id, @Nullable Theme theme )

Slide 41

Slide 41 text

@_saulmm getDrawable ContextCompat.getDrawable( @NonNull Context context, @DrawableRes int resId ) ResourcesCompat.getDrawable( @NonNull Resources res, @DrawableRes int id, @Nullable Theme theme ) AppCompatResources.getDrawable( @NonNull Context context, @DrawableRes int resId )

Slide 42

Slide 42 text

@_saulmm getDrawable ContextCompat.getDrawable( @NonNull Context context, @DrawableRes int resId ) ResourcesCompat.getDrawable( @NonNull Resources res, @DrawableRes int id, @Nullable Theme theme ) AppCompatResources.getDrawable( @NonNull Context context, @DrawableRes int resId ) VectorDrawableCompat.create( @NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme )

Slide 43

Slide 43 text

@_saulmm ContextCompat.getDrawable( @NonNull Context context, @DrawableRes int resId ) ResourcesCompat.getDrawable( @NonNull Resources res, @DrawableRes int id, @Nullable Theme theme ) AppCompatResources.getDrawable( @NonNull Context context, @DrawableRes int resId ) VectorDrawableCompat.create( @NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme ) getDrawable

Slide 44

Slide 44 text

@_saulmm getDrawable ContextCompat.getDrawable(…) ResourcesCompat.getDrawable(…) AppCompatResources.getDrawable(…) VectorDrawableCompat.create(…) ContextCompat.getDrawable(…) ResourcesCompat.getDrawable(…) AppCompatResources.getDrawable(…) VectorDrawableCompat.create(…) API >= 21 API < 21 https://stackoverflow.com/questions/43004886/resourcescompat-getdrawable-vs-appcompatresources-getdrawable Caused by: android.content.res.Resources$NotFoundException:

Slide 45

Slide 45 text

@_saulmm getDrawable ContextCompat.getDrawable(…) ResourcesCompat.getDrawable(…) AppCompatResources.getDrawable(…) VectorDrawableCompat.create(…) ContextCompat.getDrawable(…) ResourcesCompat.getDrawable(…) AppCompatResources.getDrawable(…) VectorDrawableCompat.create(…) API >= 21 API < 21 android.defaultConfig.vectorDrawables.useSupportLibrary = true. https://stackoverflow.com/questions/43004886/resourcescompat-getdrawable-vs-appcompatresources-getdrawable

Slide 46

Slide 46 text

@_saulmm @tools:sample

Slide 47

Slide 47 text

@_saulmm @tools:sample

Slide 48

Slide 48 text

@_saulmm @tools:sample

Slide 49

Slide 49 text

@_saulmm @tools:sample app/sampledata/listings.json { "data": [ { "listing": "Brand new iPhone" }, { "listing": "Garming 1030 new" }, { "listing": "Toyota Yaris 1.5 hdi 100km" }, { "listing": "Google Home Mini" }, { "listing": "Parrot toy" }, { "listing": "Lawn mower" }, { "listing": "Moving sale" }, { "listing": "Porter cable tools" }, { "listing": "Workout set" } ] }

Slide 50

Slide 50 text

@_saulmm @tools:sample

Slide 51

Slide 51 text

@_saulmm @tools:sample

Slide 52

Slide 52 text

@_saulmm @tools:sample { "data": [ { "status": “@string/title_status_unconfirmed”, "status_short" : “@string/msg_status_unconfirmed" }, { "status": “@string/title_status_confirmed, "status_short" : “@string/msg_status_confirmed” }, { "status": “@string/title_status_delivered”, "status_short" : “@string/msg_status_delivered” }, { "status": “@string/title_status_delivering”, "status_short" : “@string/msg_status_delivering” }, { "status": “@string/title_status_cancelled”, "status_short" : “@string/msg_status_cancelled },

Slide 53

Slide 53 text

@_saulmm @tools:sample { "data": [ { "status": “@string/title_status_unconfirmed”, "status_short" : “@string/msg_status_unconfirmed }, { "status": “@string/title_status_confirmed, "status_short" : “@string/msg_status_confirmed” }, { "status": “@string/title_status_delivered”, "status_short" : “@string/msg_status_delivered” }, { "status": “@string/title_status_delivering”, "status_short" : “@string/msg_status_delivering” }, { "status": “@string/title_status_cancelled”,

Slide 54

Slide 54 text

@_saulmm Generate Proguard rules https://medium.com/androiddevelopers/troubleshooting-proguard-issues-on-android-bce9de4f8a74 Directly with APK Analyzer

Slide 55

Slide 55 text

@_saulmm Generate Proguard Rules https://medium.com/androiddevelopers/troubleshooting-proguard-issues-on-android-bce9de4f8a74 Show removed nodes by proguard with Apk analyzer

Slide 56

Slide 56 text

@_saulmm Enums in Android “Think of it this way, enums are kind of like gremlins, right?” “The price of ENUMS” 3:10 Android Developers - 25 Aug 2015 https://www.youtube.com/watch?v=Hzs6OBcvNQE

Slide 57

Slide 57 text

@_saulmm Enums in Android Proguard and R8 can optimise simple Java and Kotlin enums into ints for you https://www.youtube.com/watch?v=Hzs6OBcvNQE

Slide 58

Slide 58 text

@_saulmm Enums in Android -optimizations class/unboxing/enum Proguard and R8 can optimise simple Java and Kotlin enums into ints for you https://www.guardsquare.com/en/products/proguard/manual/usage/optimizations

Slide 59

Slide 59 text

@_saulmm Enums in Android https://github.com/pyricau/fragnums

Slide 60

Slide 60 text

@_saulmm Enums in Android https://github.com/pyricau/fragnums

Slide 61

Slide 61 text

@_saulmm Proguard / R8 buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }

Slide 62

Slide 62 text

@_saulmm

Slide 63

Slide 63 text

@_saulmm Proguard / R8 buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }

Slide 64

Slide 64 text

@_saulmm Proguard / R8 buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }

Slide 65

Slide 65 text

@_saulmm @Keep import androidx.annotation.Keep @Keep data class User( @field:JsonProperty("name") val name: String // ... )

Slide 66

Slide 66 text

@_saulmm @Keep import androidx.annotation.Keep data class User( @field:Keep @field:JsonProperty("name") val name: String // ... )

Slide 67

Slide 67 text

@_saulmm onBackPressedDispatcher activity?.onBackPressedDispatcher?.addCallback(this) { } class MyFragment { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) : View? { https://developer.android.com/reference/androidx/activity/OnBackPressedDispatcher

Slide 68

Slide 68 text

@_saulmm by viewModels val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(DeliveriesViewModel::class.java)

Slide 69

Slide 69 text

@_saulmm by viewModels val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(DeliveriesViewModel::class.java) // Deprecated

Slide 70

Slide 70 text

@_saulmm by viewModels val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(DeliveriesViewModel::class.java) // Deprecated viewModel = ViewModelProvider(this, viewModelFactory) [DeliveriesViewModel::class.java]

Slide 71

Slide 71 text

@_saulmm by viewModels val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(DeliveriesViewModel::class.java) viewModel = ViewModelProvider(this, viewModelFactory) [DeliveriesViewModel::class.java] // androidx.lifecycle 2.2.0 // Deprecated

Slide 72

Slide 72 text

@_saulmm by viewModels val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(DeliveriesViewModel::class.java) viewModel = ViewModelProvider(this, viewModelFactory) [DeliveriesViewModel::class.java] // androidx.lifecycle 2.2.0 private val viewModel: DeliveriesViewModel by viewModels { viewModelFactory } // Deprecated

Slide 73

Slide 73 text

@_saulmm by viewModels val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(DeliveriesViewModel::class.java) viewModel = ViewModelProvider(this, viewModelFactory) [DeliveriesViewModel::class.java] // androidx.lifecycle 2.2.0 private val viewModel: DeliveriesViewModel by viewModels { viewModelFactory } // activity-ktx 1.1.0 // Deprecated

Slide 74

Slide 74 text

@_saulmm by viewModels val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(DeliveriesViewModel::class.java) viewModel = ViewModelProvider(this, viewModelFactory) [DeliveriesViewModel::class.java] // androidx.lifecycle 2.2.0 private val viewModel: DeliveriesViewModel by viewModels { viewModelFactory } // activity-ktx 1.1.0 // Deprecated

Slide 75

Slide 75 text

@_saulmm .map private val payment = MutableLiveData()

Slide 76

Slide 76 text

@_saulmm .map private val payment = MutableLiveData() val customer = Transformations.map(payment, Payment::customer)

Slide 77

Slide 77 text

@_saulmm .map private val payment = MutableLiveData() val customer = payment.map(Payment::customer) // lifecycle-livedata-ktx val customer = Transformations.map(payment, Payment::customer)

Slide 78

Slide 78 text

@_saulmm map private val payment = MutableLiveData() val customer = payment.map(Payment::customer) // lifecycle-livedata-ktx val customer = Transformations.map(payment, Payment::customer)

Slide 79

Slide 79 text

@_saulmm ViewStub

Slide 80

Slide 80 text

@_saulmm ViewStub

Slide 81

Slide 81 text

@_saulmm ViewStub

Slide 82

Slide 82 text

@_saulmm ViewStub

Slide 83

Slide 83 text

@_saulmm ViewStub

Slide 84

Slide 84 text

@_saulmm ViewStub “If you have parts of your UI that do not need to be visible on first launch, don’t inflate them” Developing for Android, III: The rules: Performance Chet Haase, Android Developers Jun 1, 2015 https://medium.com/google-developers/developing-for-android-iii-2efc140167fd

Slide 85

Slide 85 text

@_saulmm ViewStub private fun onNetworkError() { binding.stubNetwork.viewStub? .isVisible = true } https://medium.com/google-developers/developing-for-android-iii-2efc140167fd

Slide 86

Slide 86 text

@_saulmm ViewStub private fun onNetworkError() { binding.stubNetwork .viewStub?.isVisible = true } private fun onNetworkError() { val view = binding.stubNetwork .viewStub?.inflate() } https://medium.com/google-developers/developing-for-android-iii-2efc140167fd

Slide 87

Slide 87 text

@_saulmm Selection Tracker dependencies { // ... implementation 'androidx.recyclerview:recyclerview-selection:1.0.0' }

Slide 88

Slide 88 text

@_saulmm Selection Tracker dependencies { // ... implementation 'androidx.recyclerview:recyclerview-selection:1.0.0' } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 89

Slide 89 text

@_saulmm Selection Tracker class PoiAdapter: RecyclerView.Adapter() { private val data : MutableList = mutableListOf() init { setHasStableIds(true) } fun initSelectionTracker(recyclerView: RecyclerView) { } tracker = SelectionTracker.Builder( MainActivity.PO_SELECTION_KEY, recyclerView, StableIdKeyProvider(recyclerView), PoiDetailsLookUp(recyclerView), StorageStrategy.createLongStorage() ).build() lateinit var tracker: SelectionTracker https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 90

Slide 90 text

@_saulmm Selection Tracker class PoiDetailsLookUp(val recyclerView: RecyclerView) : ItemDetailsLookup() { override fun getItemDetails(e: MotionEvent): ItemDetails? { val view = recyclerView.findChildViewUnder(e.x, e.y) return if (view != null) { (recyclerView.getChildViewHolder(view) as PoiViewHolder) .itemDetails() } else { null } } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 91

Slide 91 text

@_saulmm Selection Tracker class PoiViewHolder(val binding: ItemRowBinding): RecyclerView.ViewHolder(binding.root) { fun bind(item: Poi, isActivated: Boolean) { binding.poi = item binding.root.isActivated = isActivated } fun itemDetails(): ItemDetailsLookup.ItemDetails = object : ItemDetailsLookup.ItemDetails() { override fun getSelectionKey(): Long? = itemId override fun getPosition(): Int = adapterPosition } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 92

Slide 92 text

@_saulmm Selection Tracker class PoiViewHolder(val binding: ItemRowBinding): RecyclerView.ViewHolder(binding.root) { fun bind(item: Poi, isActivated: Boolean) { binding.poi = item binding.root.isActivated = isActivated } fun itemDetails(): ItemDetailsLookup.ItemDetails = object : ItemDetailsLookup.ItemDetails() { override fun getSelectionKey(): Long? = itemId override fun getPosition(): Int = adapterPosition } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 93

Slide 93 text

@_saulmm Selection Tracker fun addSelectionObserver(selectionObserver : SelectionTracker.SelectionObserver) { tracker.addObserver(selectionObserver) } poiAdapter.addSelectionObserver(object : SelectionTracker.SelectionObserver() { override fun onSelectionChanged() { super.onSelectionChanged() val count = poiAdapter.tracker.selection.size() val hasSelection = !poiAdapter.tracker.selection.isEmpty // Start action mode here } }) https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 94

Slide 94 text

@_saulmm Gradle abbreviations ➜ ./gradlew assembleDev > Task :app:assembleDev

Slide 95

Slide 95 text

@_saulmm Gradle abbreviations ➜ ./gradlew assembleDev > Task :app:assembleDev ➜ ./gradlew aD

Slide 96

Slide 96 text

@_saulmm Build scans ➜ ./gradlew aD ➜ ./gradlew iD > Task :app:installDebug

Slide 97

Slide 97 text

@_saulmm Build scans // From gradle 4.3+ ➜ ./gradlew build

Slide 98

Slide 98 text

@_saulmm Build scans // From gradle 4.3+ ➜ ./gradlew build --scan

Slide 99

Slide 99 text

@_saulmm Build scans // From gradle 4.3+ https://scans.gradle.com/s/c73b5gmuvewi2 BUILD SUCCESSFUL in 1m 8s 68 actionable tasks: 68 executed Publishing a build scan requires accepting the Gradle Terms of Service. Do you accept these terms? [yes, no] y Gradle Terms of Service accepted. Publishing build scan... ➜ ./gradlew build --scan

Slide 100

Slide 100 text

@_saulmm Build scans

Slide 101

Slide 101 text

@_saulmm Rename APKs / AABs defaultConfig { // ... setProperty( "archivesBaseName", "popsy-$versionName@${rootProject.checkedCommit()}" ) }

Slide 102

Slide 102 text

@_saulmm Rename APKs / AABs

Slide 103

Slide 103 text

@_saulmm Android Build Tools android { compileSdkVersion 29 dataBinding { enabled = true } defaultConfig { buildToolsVersion "29.0.2"

Slide 104

Slide 104 text

@_saulmm Android Build Tools android { compileSdkVersion 29 dataBinding { enabled = true } defaultConfig { // This is optional from android plugin 3.+ buildToolsVersion "29.0.2"

Slide 105

Slide 105 text

@_saulmm AOSP gems /** * Used to determine whether the user making this call is subject to * teleportations. * *

As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method can * now automatically identify goats using advanced goat recognition technology.

* * @return Returns true if the user making this call is a goat. */ public boolean isUserAGoat() { return mContext.getPackageManager() .isPackageAvailable("com.coffeestainstudios.goatsimulator"); } UserManager.java:1220

Slide 106

Slide 106 text

@_saulmm AOSP gems ActivityManager.java:3375 /** * Returns "true" if the user interface is currently being messed with * by a monkey. */ public static boolean isUserAMonkey() { try { return getService().isUserAMonkey(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }

Slide 107

Slide 107 text

@_saulmm AOSP gems ActivityManager.java:2199 /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device's touch screen is capable of * tracking a full hand of fingers fully independently -- that is, 5 or * more simultaneous independent pointers. */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";

Slide 108

Slide 108 text

@_saulmm AOSP gems SensorManager.java /** Gravity (estimate) on the first Death Star in Empire units (m/s^2) */ public static final float GRAVITY_DEATH_STAR_I = 0.000000353036145f; /** Gravity on the island */ public static final float GRAVITY_THE_ISLAND = 4.815162342f; /** * A constant describing a Tricorder See * {@link android.hardware.SensorListener SensorListener} for more details. * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_TRICORDER = 1 << 6;

Slide 109

Slide 109 text

@_saulmm AOSP gems AndroidSdkVersion.java /** Android 4.3. The revenge of the beans */ public static final int JELLY_BEAN_MR2 = 18; /** Android 4.4. KitKat, another tasty threat. */ public static final int KITKAT = 19; /** Android 5.0. A flat one with beautiful shadows. But still tasty. */ public static final int LOLLIPOP = 21;

Slide 110

Slide 110 text

@_saulmm Thanks! @_saulmm