Mark L. Murphy's Blog, page 73
July 24, 2012
The Linkify Problem: The Detection and the Mitigation
As was covered yesterday, a number of HTC devices are going to have the behavior of suppressing the default chooser that would appear when we have multiple activities that could all handle the same Intent structure, for a number of common Intent structures, such as viewing Web pages.
How can we tell, at runtime, that we are running on such a device?
The best answer that I have come up with involves PackageManager, resolveActivity(), and a check for a specific response:
PackageManager mgr=getPackageManager();
Intent test=new Intent(Intent.ACTION_VIEW, Uri.parse("http://commonsware.com"));
if ("com.htc.HtcLinkifyDispatcher".equals(mgr.resolveActivity(view, PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName)) {
// you are affected
}
Here, we create a test Intent that is one affected by this Linkify fix, then use PackageManager to see who will handle it. If it comes up as com.htc.HtcLinkifyDispatcher, then we know that we are on an affected device. If it comes up null (no browser???) or something else, then we are behaving normally.
The com.htc.HtcLinkifyDispatcher package is the one that adds in this particular fix, which is why in various forums you will find recipes for removing this app from rooted devices as a way for users to get past the limitation.
Apps that expect users to be able to reach them via a chooser (e.g., Web browsers, mapping apps, streaming media players) could use this detection mechanism to let the users of affected devices know of the limitations and direct them to the “App Associations” portion of the HTC-customized Settings app, so they can make your app be the one to handle these requests.
Apps that wish to display choosers (or the equivalent) in spite of this limitation (e.g., for Web pages that have nothing to do with Linkify) could use this detection mechanism to adjust their behavior, going through some code needed for these HTC devices and not needed for others. So, if we are on a device known to block the default chooser in some scenarios via com.htc.HtcLinkifyDispatcher, what can we do about it?
There are two pieces to the puzzle of “do about it” – we have to know when we want to start an activity, and then we have to do something other than the ordinary default call to startActivity() that will no longer give the user options for how to proceed.
Sometimes, we are the ones calling startActivity() where we are used to a system-default chooser appearing.
Sometimes, while we are not the ones calling startActivity(), we could do so easily enough. An example here is with WebView. It may be that for links we do not necessary want to keep in the WebView, we would allow the default WebView processing for those links occur, to display the user’s choice of Web browser. But, we could always handle that ourselves inside of shouldOverrideUrlLoading() of a WebViewClient, so that is not too hard.
The trickiest situation, ironically, is the one that spawned this whole fiasco: Linkify. If we are using Linkify directly, or if we are using it indirectly via things like android:autoLink in a TextView, not only are we not calling startActivity() ourselves, but we do not even get control at the point in time of clicking a link to do something differently ourselves.
That is, unless we get tricky.
What Linkify does is examine text, search for particular patterns, then and convert those patterns into URLSpan objects inside of a Spannable. URLSpan just uses an ordinary startActivity() call, but we could replace those URLSpan objects with something else where we could specify what to do when the links are clicked. After all, URLSpan is simply a ClickableSpan, where we can override onClick(). And, we can use getSpans() to find the existing URLSpan objects, use removeSpan() to get rid of them, and use setSpan() to replace them with our own URLSpan alternative. TextView and android:autoLink can get more complicated, as we have to wait until TextView uses Linkify to change the text in the TextView before making our own changes to the Linkify output, or we could just skip android:autoLink and use Linkify ourselves.
Given that we can be the ones to make the startActivity() call, how do we give the user options where com.htc.HtcLinkifyDispatcher would not?
The simplest solution is to use createChooser() on the Intent class, passing in the ACTION_VIEW Intent that we ordinarily would start ourselves. createChooser() will return another Intent that we actually use with startActivity(), one that will always display a chooser if it is necessary (i.e., if there is more than one option for the Intent). This works, even on HtcLinkifyDispatcher-affected devices. And, in cases where you feel comfortable that patents are not an issue, it is fairly simple to add to your code.
If you are less comfortable with that, you may have to get more creative. After all, it is up to you and your qualified legal counsel to determine for yourselves what constitutes “display a pop-up menu of the linked actions” based upon “a user interface enabling the selection of a detected structure and a linked action”, as the terms are used in the patent that is behind all of these workarounds.
Android 4.1 (Jelly Bean), for example, slightly changes the chooser, to show a set of icons in a row or grid for the chooser, instead of a classic vertically-oriented menu-ish list. Whether this change was introduced based upon this patent, we may never know. But, you could consider similar sorts of light UI changes to the chooser structure:
Use a GridView of icons
Use a HorizontalScrollView of buttons in a horizontal LinearLayout, for a “swipe to find the app you want” motif
Use a Spinner, with some likely default choice, forcing the user to take an additional step to “display a pop-up menu of the linked actions”
Use a quick-actions pop-up bubble
And so on
Or, you could try to break the pattern entirely:
Upon clicking the link, raise a Notification that, in turn, displays some sort of chooser
Upon clicking the link, add a dynamic ShareActionProvider to your action bar that will display the options for the last-clicked-upon link
These sort of options more dramatically increase the separation between “user interface enabling the selection of a detected structure” and the “pop-up menu of the linked actions”, albeit at the cost of usability. And, again, it is up to you and qualified legal counsel to determine whether such steps as this are sufficient for your needs.
Many people complain about Android fragmentation, thinking of mundane things like screen sizes and densities. However, it is changes like HtcLinkifyDispatcher that represent the real fragmentation. Sometimes, we as developers simply see the fragmentation and assume the issue is the result of “pointed-haired bosses” at a device manufacturer, or some such. And, most likely, that is the case from time to time, as any firm the size of major device manufacturer will undoubtedly have some bosses whose hair is decidedly pointy.
But the HtcLinkifyDispatcher situation is one where the fragmentation is being triggered by external forces – effectively, by “pointy-haired bosses” from outside the Android ecosystem. Regardless of the origin of the fragmentation, though, we as app developers must do as we have always done: find these things, make sure we are aware of them, and cobble together workarounds as needed.
July 23, 2012
The Linkify Problem: The Patent and the Behavior
We as Android developers will start to see functionality regressions on various devices, courtesy of software patent disputes between Apple and several Android device manufacturers. We are already seeing this with newer HTC devices shipped in the US, and it is well within reason that other manufacturers will follow suit in coming months, albeit in their own ways.
In February of 1996, Apple Computer, Inc. filed a US patent application – granted in 1999 – entitled “System and method for performing an action on a structure in computer-generated data ”.
Now, for the record, IANALNDIPOOTV (I am not a lawyer, nor do I play one on TV). If you want a detailed examination of this patent, please consult with qualified legal counsel, not a blogger.
That being said, in a nutshell, this patent covers, among other things:
A computer detecting various “structures of data”
Associating various actions with those structures
Displaying a pop-up menu to allow the user to choose one of those actions
In the specific case of Android, Apple is complaining that this patent covers the behavior powered by the Linkify class and used in various places, such as when you set android:autoText on a TextView. In those cases:
Android detects things like email addresses and URLs, via regular expressions and makes them clickable
Upon a click, Android crafts an ACTION_VIEW Intent for the clicked-upon item and calls startActivity() upon it
This startActivity() call may produce a pop-up menu, in the form of a chooser, if there are multiple possible activities that could respond to that Intent
Now, normally and historically, this would be a matter for the US court system to adjudicate, and that will presumably happen someday.
However, Apple has filed claims against Android device manufacturers with the US International Trade Commission (ITC for short). ITC has the power to ban imports of products that might involve “intellectual property” violations, if the ITC believes that the complaintant would be “irreparably harmed” by those imports continuing while the legal process churns along.
ITC’s efforts blocked import of a number of HTC devices.
HTC made modifications to their devices to attempt to eliminate the infringing behavior, and ITC has since allowed HTC to sell these modified Android devices in the US. Apple feels that HTC’s efforts were insufficient, and it is entirely possible the import block will return someday. It is also emininently possible that other ITC acts over this patent will affect other device manufacturers, and these manufacturers will make their own modifications to Android to try to get around the import block.
You may feel that software patents are a valid legal construct, or you may not. You may feel that this particular patent is invalid, or you may not. Regardless, what is indisputable at this point is that this patent is being applied and, as a result, select Android devices are changing their behavior. And, unfortunately, those changes affect us as developers.
Normally, on an Android device, if you click on a link that shows up in a TextView augmented by android:autoText, some activity will appear based upon what you clicked. A browser will appear for URLs, a mapping activity will appear for an address, and so forth. None of that has changed.
However, suppose you have two or more browsers, or you have two or more mapping applications, and you click on one of those links. On a normal Android device, a chooser will appear… but that is the behavior that Apple is asserting violates its patent.
On various US-sold Android devices, such as the HTC One S, you will not get a chooser. Instead, it will open one of your possible choices automatically. By default, it will be whatever app for this type of link came with the device, though you can go into an “App associations” area of the Settings app and configure which of your various applications should handle each of the restricted types of links:
Phone numbers
Email addresses
Physical addresses
Web URLs
“Map addresses” (presumably geo: URLs, but that is unclear)
Streaming links (presumably rtsp: URLs, but that too is unclear)
“Online audio download by webview”
“Online video download by webview”
On the surface, this may not seem too bad. It’s basically a return to classic Microsoft Windows behavior, where you used to have to go in and specify which application should handle which file extension.
However, the scope of the behavior change is a bit of a problem. You might think that it would only affect stuff powered by Linkify: direct use of the Linkify class, android:autoText, and so on.
You would be mistaken.
It affects everything that tries to perform an ACTION_VIEW on any of those URL types. Whether that comes from WebView, or you invoking startActivity(), or links created by Html.fromHtml(), or anything else does not matter. None of those will display a chooser if there is more than one choice and the user has not specified a default activity for that action, the way it will on other devices.
Hence, developers of browsers, email clients, mapping applications, media players, and the like, who might be expecting the user to choose to use their apps for handling these sorts of URLs will be disappointed. User can choose their apps, but they have to do so via Settings, not via a chooser. In fact, they may not even realize that they can make this sort of change, particularly if they are new to Android and are unaware of this whole chooser thing. After all, since the vast majority of devices do not have an “App associations” portion of Settings, the user is unlikely to run across references to it in blog posts and the like.
This can also impact developers who are calling startActivity() for these URL structures, as where you might have expected a chooser to appear as needed, none will on these devices.
Also, bear in mind that this is HTC’s response to the problem. Other manufacturers may make different changes as they attempt to get around the threat of import bans.
Tomorrow, I will point out how we can detect affected devices and some ways in which we can mitigate the damage.
July 17, 2012
Accentuate the Positive (Navigation)
To quote Juhani Lehtimaki:
If it takes almost an hour to explain developers how to handle back it simply cannot be easy enough for users to understand. To me the biggest problem with the back button is its inconsistent behavior without any visual hint of the currently active state of the back button.
The BACK button, like many things, is simple in simple cases, and gets complicated in complicated cases. Google’s recent efforts seem to have focused on trying to handle some complicated cases, and that’s perfectly fine as far as it goes.
However, in many apps, there is a simpler solution: avoid making BACK be a load-bearing part of your navigation. Rather than have users rely on BACK (negative navigation), make sure you have means to allow users to tell you where in your app they want to go (positive navigation). Use BACK for “get me outta here” requests (e.g., escaping a modal dialog) and system-defined navigation (e.g., closing the input method editor), and try to have BACK make sense in other cases, but worry more about ensuring users can more positively state what they want to see.
The action bar can be a key tool for helping with this, by giving your app a common spot for navigation elements (buttons, overflow menu items, etc.). If need be, investigate using the “side navigation” or “ribbon” pattern used by various apps, if you feel that your navigation options cannot reasonably fit within the action bar, or would not be intuitively obvious when shown in action bar-sized icons and such. And elements like ViewPager can allow for positive navigation within a peer set of elements (e.g., swiping through contacts), without relying upon the BACK button. And so on.
This may also involve heavy use of FLAG_ACTIVITY_REORDER_TO_FRONT, so you avoid creating extra copies of activities for each navigational request from the user. Or, if you are using lots of fragments in fewer activities on tablets, implement the appropriate transactions that allow you to keep the fragment around (e.g., attach and detach) so you can reuse it later if navigation requests warrant.
If you think about the Web apps that you use, ponder which of those you find yourself using the browser’s BACK button, and which of those you do not. Then, see how you can take the design lessons from the ones that do not rely upon BACK and apply those to your app.
After all, the user can only be confused by BACK if the user ever presses it.
July 12, 2012
READ_LOGS Regression
As of Jelly Bean, ordinary SDK applications cannot hold the READ_LOGS permission. Only applications that are signed with the firmware signing key or are system apps (e.g., pre-installed apps) will be able to hold this permission at the present time.
This is a security measure, partly to prevent apps from reading data leaked into LogCat by other apps, and partly to prevent apps from using LogCat data to infer what is going on with the device and harming the user. These techniques — along with others that I will not mention here — are being used to do such things as attempt to block uninstallation of apps. I have personally filed security issues regarding this sort of behavior.
The primary cost to developers is that often times the error information we seek from devices in the field is logged to LogCat by the OS or by device-specific apps. Right now, our primary workaround for that sort of information is the obscure POWER + VOLUME UP + VOLUME DOWN key combination, which triggers an ACTION_SEND that can be picked up by email clients and contains a copy of a dumpstate report, plus a screenshot. However, this is very clunky to initiate (it took me several tries on a Galaxy Nexus). I have filed a feature request to try to make this easier for users.
For your own logging information, you should consider switching from LogCat to something else that logs to your internal storage, which you then arrange to send in your ACRA reports and the like. In addition to the java.util.logging built into Android, you can integrate any number of third-party libraries.
For more information about this issue, including Dianne Hackborn’s comments, please see this android-developers thread.
July 10, 2012
Don't Advertise Intent Filters That Are Not Yours
About 12 hours ago, somebody posted a question to StackOverflow that hits upon another Android anti-pattern: copying and pasting <intent-filters> without thinking.
The UPS Mobile app allows you to track packages and do a handful of other things that you might ordinarily do via the UPS Web site. It generally seems to be well-regarded, but it has an annoying flaw:
It claims to be Barcode Scanner, and does a lousy job at it.
Barcode Scanner, from ZXing, is a favorite among Android developers for its integration possibilities. However, some people do not like having a dependence upon the Barcode Scanner app, so they grab the open source code and attempt to blend it into their own apps. This is neither endorsed nor supported by the ZXing team, but since it is open source, it is also perfectly legitimate.
However, UPS (or whoever they hired to build the app) screwed up. They not only copied the source code, but they copied the manifest entry for the scanning activity. And, their activity has:
<intent-filter>
<action android:name="com.google.zxing.client.android.SCAN" />
<category android:name="android.intent.category.DEFAULT" />
<intent-filter>
This means that on any device that has UPS Mobile installed, they will be an option for handling Barcode Scanner Intents. What happened was that the person asking the question was manually invoking startActivityForResult() to bring up Barcode Scanner, was getting a chooser with UPS Mobile in it, and then was crashing upon choosing UPS Mobile… because UPS Mobile declared this activity to be not exported.
Due to this bug, Android will display non-exported activities in a chooser, despite the fact that they can never be successfully used by the user.
So, what should we learn from this?
First, UPS Mobile should not have used that <intent-filter>. As Dianne Hackborn has pointed out, your <intent-filter> mix is effectively part of your app’s API, and so you need to think long and hard about every <intent-filter> you publish. UPS Mobile is not Barcode Scanner and should not be advertising that they handle such Intents, despite the activity being not exported.
Second, UPS Mobile probably should not have had any <intent-filter> elements for this activity, if they intend to use it purely internally. They could just as easily use an explicit Intent to identify the activity and avoid all of this nonsense.
Third, the person who filed the SO question ideally would have been using ZXing’s IntentIntegrator. As Sean Owen of the ZXing project noted in a comment on my answer, IntentIntegrator ensures that only Barcode Scanner or official brethren will handle any scan requests, so this problem would not have appeared.
Fourth, Android really should not be showing non-exported activities in a chooser, which means probably that PackageManager should be filtering out non-exported activities from methods like queryIntentActivities(), which I presume lies at the heart of the chooser.
July 8, 2012
The Busy Coder's Guide to Android Development Version 3.8 Released
(I thought I might never get around to writing that blog post title…)
Subscribers now have access to the latest release of The Busy Coder’s Guide to Android Development, known as Version 3.8, in all formats.
This is the culmination of The Big Book Reboot — the merger of all my Android books into one, 1,651-page digital tome.
Compared to Version 0.8 of the former Android Programming Omnibus, this edition has ported over most of the rest of the material from the other titles. It also adds the first portions of the “Widget Catalog”, a series of short chapters covering one widget apiece, designed to provide samples, screenshots, and tips to complement the SDK’s JavaDocs.
In addition, this edition provides:
A partially rewritten Advanced ListViews chapter
Updated action mode coverage to incorporate ActionBarSherlock (where possible)
A partially rewritten chapter on advanced permissions
Partially rewritten coverage of PowerManager and WakeLock
More coverage of preferred activities and activity resolution
A partially rewritten chapter on JUnit
Light coverage of MonkeyRunner
This edition drops coverage of using the Camera class, formerly in The Busy Coder’s Guide to Advanced Android Development. Like sensors before it, I no longer feel confident in describing the details of getting the camera to work, particularly across devices. There is camera coverage in the book, focusing on Intent-based solutions and ZXing for barcode scanning. I hope to restore the Camera coverage – probably as part of publishing a CWAC project to help simplify its use – later in 2012.
People who were subscribed as of 8pm Eastern on July 7, 2012 will have access to the new book edition, plus access to the last-published editions of The Busy Coder’s Guide to Advanced Android Development, Android Programming Tutorials, and Tuning Android Applications. New subscribers will only have access to The Busy Coder’s Guide to Android Development.
My plan is to attempt to update this book monthly, typically around the first of the month. The next update – Version 4.0 – will add in coverage of the new Jelly Bean Android release, assuming that I have gotten my hands on Android 4.1 hardware.
Note that The Busy Coder’s Guide to Android Development is eligible for the Book Bug Bounty program. I suspect that there are lots of bugs in there. :-)
If you encounter any problems with this update or your Warescription in general, please contact wares@commonsware.com, and be sure to provide your Warescription user ID.
July 5, 2012
Who Will Become Android's Red Hat?
Android really could use a Red Hat.
By this, I mean Android could use a firm that the enterprise and bespoke hardware vendors can turn to for Android firmware, just as Red Hat, particularly in Linux’s early days of adoption, was the go-to firm for enterprise Linux. While other groups had Linux distros, and later firms added an enterprise push (e.g., Canonical), Red Hat was the first serious firm to say “we’re here for business, and will help ordinary consumers and developers some along the way”.
What might a Red Hat for Android look like?
It would have its own Android distro, based off of the AOSP, with a focus on security, privacy, and clean IP (i.e., no blatant copyright infringement with respect to what it distributes)
It would sell this distro into enterprises, along with tools to allow the enterprises themselves to “remix” the distro, adding in their own stuff (e.g., VPN apps and configurations, enterprise-specific apps)
It would create apps with a business focus that it would sell (or simply include) in the distro (e.g., privacy-enhanced home screens)
It would help enterprises get their remixed distro onto hardware, ranging from white-label devices to major-brand ones (where supported)
It would make simplified versions of these tools and services available publicly, for brand-building as much as anything else (the equivalent of Red Hat’s Fedora), particularly for those firms looking to create dedicated “kiosk” devices (e.g., a restaurant wanting its menus and ordering to be on dedicated tablets)
It would add in other services (e.g., consulting and training) to be able to fill in whatever gaps its enterprise customers required
It would try to become enough of a friend of the ROM modding community that ROM modders would help advance the firm’s Android distro, particularly in cases where they cannot contribute back to the AOSP due to the constraints of Google’s development practices
I am of the firm belief that enterprises would flock to this solution. Right now, enterprises are trying to skate by with using whatever device the employee already owns, but there will be enough high-profile security breaches that they will sour on this approach eventually. Given a choice between being stuck with the firmware from Apple, Microsoft, or an Android manufacturer, or having one that they have visibility into and more control over, enterprises will tend towards the latter. After all, enterprise IT departments do that for just about everything else.
Who might become the Red Hat of Android?
It could be an existing device manufacturer who spins off a subsidiary that takes on this model. The parent firm would stick with the low-margin hardware business, while the subsidiary would be in the higher-margin software business. Eventually, they might float an IPO for the subsidiary (“unlocking the latent value” – Wall Street types love this stuff)
It could be somebody with deep experience in enterprise product and service sales that adds a division or outright pivots into the mobile space. An example would be Red Hat itself: one would imagine that they have to be thinking about how they can enter into mobile.
It could be some new high-flying venture-backed startup, poaching a few engineers from Google and device manufacturers, and perhaps the ROM modding community, to serve as the core team.
It could be the result of a takeover of RIM – the last remaining substantial enterprise platform – where they dump RIM’s operating systems, repackage Blackberry Messenger as an app, and switch wholesale to Android, to try to leverage the remaining brand recognition and enterprise sales and marketing teams.
It is unlikely that Google itself will become the Red Hat of Android, insofar as enterprises have not exactly been Google’s cup of tea in terms of a business focus. Theoretically, it could be a Google spinoff, but I can’t think of any Google spinoffs, beyond the traditional employee-leaves-to-found-a-startup model.
With every successful platform (Google, Apple) having headed in the consumer direction, and every successful enterprise-focused platfom either dying (RIM) or moving to the consumer segment themselves (Microsoft), there seems to be a big market opportunity here. With luck, a firm that succeeds in this segment really will be “Android’s Red Hat”, where the business works in concert with the larger Android ecosystem, as opposed to against it.
July 3, 2012
Jelly Bean, Renderscript, and Deprecation
If you peek through the release notes for Jelly Bean, you will notice the following statement:
Note: The experimental Renderscript graphics engine is now deprecated.
And, if you look at the package summary for Renderscript, you will see a variety of classes are marked as deprecated.
After a minor dustup on the Android Developers Google Group, we did get some clarification.
Basically, Renderscript has long been broken down into two halves: the graphics engine and the compute engine. Initially, the graphics engine is what got all the attention. However, as Dave Burke indicated:
The feedback we got from internal and external app developers was that they preferred to use OpenGL directly because of familiarity & portability either through our Java bindings or NDK.
As such, the graphics portion of Renderscript is deprecated, meaning that it still works, and they will try to maintain it going forward, but you should consider alternatives. And, given the nature of Renderscript, I would find it to be more likely than average to be completely discontinued in some future release due to technical limitations.
Note that the compute engine in Renderscript is still going strong, to allow implementation of faster algorithms without having to go through the NDK.
June 27, 2012
ADT R20 and My Book Schedule
Lost in the hoopla over the release of the Android 4.1 (Jelly Bean) release was the delivery of the R20 version of the Android tools and the ADT plugin.
The ADT plugin has substantially changed. Again. The new-project wizard is totally revamped, more files and code are automatically added to your initial projects (even if you eschew their more elaborate app templates), and the behavior of the layout editor is substantially changed. And that’s all I’ve noticed so far, in a frustrating couple of hours of trying to make sense of READ_EXTERNAL_STORAGE.
I now have to rewrite several chapters to cover the changes.
Right now, I am projecting that I will ship an updated book, sans any Jelly Bean coverage, late next week. In all likelihood, that will be branded as The Busy Coder’s Guide to Android Development Version 3.8 and will replace the other titles at that point. A Version 3.9, containing Jelly Bean coverage, will be in late July, depending upon the availability of hardware.
But, for all I know, more surprises are in store for me, so these projections are not exactly carved in granite.
June 26, 2012
I, For One, Welcome Our New 1080p Tablet Overlords
While everyone is thinking about rumored Nexus tablets, let’s talk a bit about something that’s dead certain: 1080p (1920 x 1200) tablets are here.
While much focus has been placed on the impending Asus Transformer Pad Infinity (or whatever they’re calling it), the Asus Iconia Tab A700 is shipping now. Here in the US, it’s available for $450 from various online retailers, including those named after large South American rivers.
A 1080p tablet ties Google TV as the largest resolution Android is generally seen upon. And, as always, when we start pushing the proverbial envelope, issues crop up.
First, as Michell Bak pointed out a couple of days ago, the Android emulator does not support 1080p, with the exception of the Google TV emulator. Since the Google TV emulator requires Linux, that will cause issues for others. The behavior of the emulator is reminiscent of the pre-tablet era when trying to get it to run at WXGA resolutions. One can only hope that this gets addressed in the not-too-distant future.
Then, we have all the developers who assume that -xlarge devices are WXGA tablets, just as we had developers assume various other size or density buckets imply a specific resolution.
If the Iconia Tab A700 is representative of other 1080p tablets, you can expect:
screen size of -xlarge
density of -hdpi
-notlong for screen aspect
1920 x 1128 pixels in landscape, 1848 x 1200 in portrait
Since these devices are just rolling out now, they will be tiny percentages of your user base. But, it’s something to keep in mind, particularly if you have resolution-dependent logic within your app.


