I recently got a new laptop; a Dell XPS 13. It's pretty swanky, but it isn't a MacBook, which is a first for me in quite some time. That's meant getting used to a new physical construction, a new keyboard, a new trackpad, and even a new operating system! I installed Elementary OS because I wanted a Linux, and I wanted it to be simple and easy to use. So far, it's mostly lived up to these things, which has been great.
I've generally been really happy with both the laptop and the OS, but one of my annoyances is that it doesn't have dedicated buttons to make the screen brighter or dimmer; you can click the power indicator and get a scroll-bar for brightness, but that's a little clicker than I'd like. So I did some learning...
It turns out there's a file through which you can control brightness: /sys/class/backlight/intel_backlight/brightness - it contains a number between 0 and 7500 (inclusive) which shows your current brightness level, and you can write a number in that same range to the file to set the brightness level. It can only be written to by root.
The Keyboard settings pane has an option to set shortcuts, so you can say "If I press control and the NextTrack button, run this command".
So, the answer seems to be: Write a tiny little program which updates that file, make it setuid-root (so that when anyone runs it, it runs as root), and set up a shortcut which runs it.
So I wrote a tiny little program (in Rust). Note that this needs to be setuid-root, which means it needs to be carefully designed. Scripts aren't allowed to be setuid-root, so it needs to be a compiled program. And it keeps its user input space pretty minimal; the path it's writing to is hard-coded (if it took it as a parameter, people could write to arbitrary files as root - scary!), it takes instructions like "up" and "down" rather than take a value to set (to avoid needing to sanitise input), and it crashes out aggressively if anything goes even a little bit wrong.
I built it, by running (after installing the rust toolchain as per https://rustup.rs):
cargo build --release
made it setuid-root by running:
sudo chown root:root target/release/set-brightness
sudo chown 4755 target/release/set-brightness
put it on the $PATH by running:
sudo mv target/release/set-brightness /usr/local/bin/
and created my shortcuts by opening up System Settings, going to Keyboard, clicking Custom, clicking the +, entering the command set-brightness up, clicking where it says "Disabled", and pressing the shortcut keys I wanted.
Now I have "brightness up" and "brightness down" keyboard shortcuts, and life is good :)
Sunday, 2 September 2018
Monday, 11 June 2018
What I want from a code review tool
Many of my friends and colleagues will have heard me decry the state of code review tooling in the open source world. Here, I try to collect my thoughts on what I feel is missing; maybe it will help motivate some of the big players in that direction, maybe it will serve as a roadmap for my next side-project.
I'm going to assume that as a reader, you're bought into the general idea of code review: a place for people to get constructive feedback about a change they want to make (a "Pull Request") before that change is merged onto a target branch.
So what do I want from a code review tool?
As a code author, I want to easily see the status of my change. What needs to happen before I'm comfortable merging it? Some examples:
I'm going to assume that as a reader, you're bought into the general idea of code review: a place for people to get constructive feedback about a change they want to make (a "Pull Request") before that change is merged onto a target branch.
So what do I want from a code review tool?
As a code author, I want to easily see the status of my change. What needs to happen before I'm comfortable merging it? Some examples:
- Have my reviewers looked at the code yet?
- If not, I probably don't want to merge my code (obviously, there are exceptions).
- There is some nuance here - as a code author, am I just looking for someone to look over my code, just to sanity check it? Am I looking for Janet to review this code, because we had a discussion about the design, and she's best placed? Do I also want Sophie to also take a look, because she probably knows a better language construct I could be using? Whose review is sufficient?
- There's more nuance that gets added from the reviewer side; I've yet to use a code review tool which has relationships between reviewers: "I'm ok with it if Sophie is". It's hard to express these concepts elegantly, but I suspect it can be done.
- Have any of my reviewers left comments which I haven't addressed? Which comments still need addressing?
- My general rule of thumb is that all comments should either be addressed in code, or explained as to why they're not.
- There is some nuance here - some comments are trivially addressable; some may need discussion, or I may try to address them, but not do a great job of it, and need follow-up. Ideally, the nuance of "I've left a comment, feel free to ignore it" vs "I've left a comment, I'm sure you'll address it fine" vs "I've left a comment, but would like to have another look before it's considered addressed" would be captured, in a low-friction way.
- If any tests have been run, have any failed? If so, I probably want to investigate that before I merge my code.
I've yet to use a code review tool in the open source world which actually meets these three criteria. Most shy away from modelling the social interactions and connotations of code review (things like "I'm ok with it if Sophie is"), and instead try to dump all of the information that could be relevant at you. Saying in one place "Janet has accepted, Sophie hasn't reviewed yet", and in another "Janet mentioned half-way through a paragraph that Sophie should probably take a look). Worse - many of them hide information they deem out of date. When nuance can only be expressed through text, it's great to have the text there for people to read, but wouldn't it be nice if a green tick meant "Good to merge", rather than maybe "Good to merge" or maybe "Good to merge if you make this change" or maybe "Good to merge but it would be great for Sophie to have a look too"?
As a code reviewer, I want to see what I need to do to unblock people.
- Has someone asked for my review, which I haven't provided?
- Has someone responded to one of my comments?
- Has someone acted on one of my comments where there's follow-up I should do?
Within the tool, there are two very distinct conversations to have; "Is this change generally sensible" and "Are the specifics of the code sensible?" - fortunately, the existing tools seem to cover this pretty well, with "change comments" and "line comments".
Now that the general concepts are covered, what specifics do I want in a code review tool?
- High quality code browsing.
- Syntax highlighting.
- Click-through and find-usages of symbols.
- High quality diffing.
- Code diff chunks should include the name of the function, and type, the code is in.
- Reviewing similar images? Show a diff!
- Context of wider change.
- Is there a ticket/issue describing the larger-scale work that this change is part of? Link to it! Render some text from it!
- Are there follow-up changes? Does it build on previous changes?
- Does this change depend on other pending changes? Should they be merged atomically?
- Ideally changes should be very small, but navigable with the right context to understand the bigger picture. Being able to change that level of granularity ("show me the change to this file in this pull request", "show me the whole change of the whole stack of dependent pull requests") would be nice, too.
- Previews.
- Editing a markdown document? Render a preview, and render a diff!
- Editing a web frontend? Host a preview of the site somewhere, and link to it.
- An elegant, at a glance view of what I need to do, both per-review, and across all reviews.
- Reviewers should be able to give code-edit suggestions.
- Double-clicking on the code, fixing the typo, and presenting the code author with an "Accept suggestion" button is so much lower friction than saying "nit: It's just a typo, but there's an extra s here", and then requiring the code author to change branch, make an edit, commit, push, and say they've fixed it.
- Automated reviewers should be easy to plug in.
- If I can write a tool which detects problems, I can save humans time.
- Doubly so, if they can propose code-edit suggestions which can be accepted at a button-press.
- There are a few classes of these; "always correct" suggestions vs "maybe a good idea" suggestions.
- Merging code by pressing a button in a web UI is nice.
- Having an option to merge automatically when reviewers are happy is even nicer.
What don't I want in my code review tool?
- Anything to do with the specifics of a version control system.
- Maybe the system ingests things via git push or ingesting a patch file, and maybe it can merge things onto a branch, but those should be the only times version control matters.
- Visual clutter.
- Everything I want to know about a review should be quick and easy to discern at a glance. I should be able to dig in deeper when I need/want to.
- An overly simplified, or overly strict, model of review.
- Different projects, teams, companies, and people, use different models for code review requirements, and code ownership. Code review tools all seem to either treat all reviewers as equal, or assume that each reviewer ticks a certain box (generally "owns directory X"). If what I want to say is "I'm happy with this code, but Sophie should check it over", or "I'm happy with this code, but here are some trivial things you should address first", or "Two people should review every change", I want that to be obvious.
There are also some crazy ideas that I'd love to see, but which sound hard:
- AST-aware formatting, diffing, and history.
- It's crazy that people need to have style guides, and style wars, over when lines should wrap, or how much things should be indented. "Lines" of code is a weird concept. Statements and expressions are the building blocks of code, not lines!
- Semantically aware change decomposition.
- You renamed a field, then extracted a function, then added a new call to the function. Wouldn't it be cool if your code review tool could tell your reviewer that's what you did, rather than them inferring it from reading?
Sunday, 3 June 2018
Rust minimum versions: SemVer is a lie!
A couple of months ago, cargo got a new feature I've been wanting for a while; an option to select the minimum, rather than maximum, compatible versions of each of your (transitive) dependencies, according to semver. So if you say you depend on foobar version 1.1, it will pick 1.1.0 not 1.1.16, as opposed to the standard behaviour of choosing 1.1.16. By traditional wisdom, this isn't something you actually want to use in real life, because newer versions tend to have optimisations / security fixes / ... which older versions don't. But it's a useful tool for checking whether your declared supported dependency versions are accurate.
This has particularly been in my mind recently because of Russ Cox's recent work on semantic versioning dependencies in Go. The tl;dr of that is: Avoid making breaking changes, libraries specify their minimum supported version of their dependencies, and the Go tooling will choose the minimum supported version of each dependency which works for the transitive set of dependencies. It does exactly the thing that traditional wisdom says is bad, but Russ argues pretty convincingly that this is good [1]. However, Russ's arguments rely on this being a community-wide adopted standard practice; if the community isn't all using the minimum supported version of things, it doesn't work, because nothing advances the base version of libraries, and everyone ends up stuck with super old versions of everything.
I thought I'd see how minimum versions worked out in the Rust ecosystem. I used my current main Rust project, the scheduling engine of the Pants build tool, as my playground. The project is about 50kloc, and has 134 transitive dependencies. Those dependencies include a handful of fairly common pure-rust libraries (clap, futures, tokio), a handful of C/C++ libraries (lmdb, grpcio, fuse), and a long tail of others.
I was expecting some things not to work, but I was mostly expecting to just need to bump some versions because libraries were using features introduced in newer minor versions than they claimed. I was surprised by some of the ways that things didn't work, and how hard it was to fix them...
1: Russ Cox's series of blog posts is very interesting, and well written, and I recommend giving it a read if you're interested in this kind of thing. I have mixed opinions on the design in general, but that's a conversation for a different day!
This has particularly been in my mind recently because of Russ Cox's recent work on semantic versioning dependencies in Go. The tl;dr of that is: Avoid making breaking changes, libraries specify their minimum supported version of their dependencies, and the Go tooling will choose the minimum supported version of each dependency which works for the transitive set of dependencies. It does exactly the thing that traditional wisdom says is bad, but Russ argues pretty convincingly that this is good [1]. However, Russ's arguments rely on this being a community-wide adopted standard practice; if the community isn't all using the minimum supported version of things, it doesn't work, because nothing advances the base version of libraries, and everyone ends up stuck with super old versions of everything.
I thought I'd see how minimum versions worked out in the Rust ecosystem. I used my current main Rust project, the scheduling engine of the Pants build tool, as my playground. The project is about 50kloc, and has 134 transitive dependencies. Those dependencies include a handful of fairly common pure-rust libraries (clap, futures, tokio), a handful of C/C++ libraries (lmdb, grpcio, fuse), and a long tail of others.
I was expecting some things not to work, but I was mostly expecting to just need to bump some versions because libraries were using features introduced in newer minor versions than they claimed. I was surprised by some of the ways that things didn't work, and how hard it was to fix them...
Language stability
For the last couple of years, Rust has felt like a relatively stable, but quickly improving language. If you look through the "Compatibility Notes" and "Breaking Changes" section of the release notes since 1.0 (now 3 years ago), pretty much everything is a minor and niche issue; the language has been adding many things, but rarely breaking old ones.
There has been some discussion as to whether the minimum version of rustc you need to compile with should be considered part of a crate's API, and whether increasing that value should require a major version change. I was surprised, however, to find that the opposite was a common problem when pinning to minimum versions - the minimum supported semvers of some of my transitive dependencies are so low that they don't compile with recent (or for some, any post 1.0) versions of rust!
Some examples, with rust 1.25:
- log is, surprisingly, a problematic crate. One of my dependencies specifies a '0.*' dependency on log, I guess because it doesn't want to be the most constraining dependency for something it doesn't care much about - the antithesis of the requirement for the whole community to bump minimums of Russ's proposals for Go. This means that I actually end up with all of log 0.4.1, log 0.3.1, and log 0.1.0, all in my application. But log 0.1.0 uses regex 0.1.0, which was released in 2014, pre rust 1.0, when Rust was a very different language. But it's not obvious what this crate should be doing. Technically, if you found the right compiler, log 0.1 would probably be fine for this library. In all practical terms, it doesn't work with log 0.1, because I suspect there isn't a version of the compiler that supports both regex 0.1.0 and the language features of my dependency. What version should it specify? Should it find whichever version of log's transitive dependencies built with rust 1.0, and specify that? Or rust 1.12? Or rust 1.20?
- rand 0.3.13 (released January 2016) doesn't compile, because it depends on winapi 0.0.1, again released in 2014. I guess it was useful to find out that the protocol buffer compiler I'm using is using a two year old crate for making temporary files, which itself depends on rand 0.3.13 (and that there are much more commonly used crates for this purpose), but am I really going to send them a PR moving them over to use tempfile instead? Maybe... Maybe switching to actively maintained crates with stable versions is beneficial to the ecosystem. But that feels like it would be a surprising PR to receive, and certainly isn't the base expectation of the Rust community today.
- All of my C/C++ dependencies end up depending on pkg-config 0.3.0, which doesn't work with any rust since 1.0. But eeeeeverything does that! libz-sys, libgit2-sys, libssh2-sys, lzma-sys, libsqlite3-sys, libsodium-sys, ffmpeg-sys, libdbus-sys, glib-2-0-sys, and dozens of other widely used libraries maintained by people who know what they're doing, specify their pkg-config dependenciy as ^0.3. Are they all doing something wrong? Surely not...
Fixing these things is hard
I got my library compiling, eventually, but it took a lot of effort, and required forking several projects. Let's take a look at the log example. Ideally, I should somehow be able to say "I don't care what my dependencies say they need, give them log 0.4.1, that's the log I want to use". I found the [patch] section which looked like it did what I wanted, but it doesn't appear to cover transitive dependencies. Maybe there's something I missed, here?
So I started forking all of my dependencies which themselves had problematic dependencies. Ideally I can get PRs merged to update these things, but it's not obvious that "increase the minimum version you specify so that the minimum version actually compiles on modern Rust" is a reasonable pull request; partially because rust versions don't factor into versioning anywhere, and partially because minimum versions aren't expected to be used, even though they may technically be expected to work. Again, it feels like a weird PR to receive.
Does this matter?
Maybe this doesn't matter, in practical terms. Cargo prefers the most recent available version, and that mostly works for people. And maybe this whole problem is just a relic of pre-1.0 to post-1.0 transition, and it will go away at some point. But it seems strange that we bother to go through all of this writing down semvers of our dependencies, only for them to frequently be lies. Maybe Russ Cox is right, and you need to force people to keep them accurate by actually using the minimum versions. Maybe we should give up with specifying minimum versions at all, and just always use the most recent version (relying on Cargo.lock files for reproducibility). Maybe now that we have -Z minimal-versions, it will be easy to add these checks to people's CI (or even on the crate publishing path), and we can enforce that what we write down is accurate. Maybe this is yet another reason we should be factoring "supported rust versions" into dependency resolution (either by including it in semver, or by tracking it separately in Cargo.toml files). I don't know what we should do as a community, but right now, things feel a little weird.
1: Russ Cox's series of blog posts is very interesting, and well written, and I recommend giving it a read if you're interested in this kind of thing. I have mixed opinions on the design in general, but that's a conversation for a different day!
Monday, 8 January 2018
Rust: Lacking equivalence under simple refactorings
I've been writing a fair bit of Rust recently. I'm new to the language, and it's been alternating between making me very happy, and making me very frustrated. One of the things I've been finding frustrating is the number of places that I expect two pieces of code to be equivalent where they are not. Here's some sample code, which I'm going to use in a few examples:
First, let's look at call_one and call_two. These look like they should be identical code; the simple application of an "inline" refactoring (or "extract variable", if going in the other direction). But in reality, call_two is fine, but the borrow checker is unhappy with call_one:
So what's going on here? When you declare a variable with let, its lifetime begins, and it remains live until the variable it's bound to goes out of scope. But if you never bind an expression to a variable with let, its lifetime immediately ends, and it gets dropped. There seem to be some special-cases where this is transparently handled for you (look at call_three - it's exactly the same as call_one which the borrow checker finds problematic, but the borrow checker is fine with this one. The only difference is that bar borrows self mutably, whereas foo doesn't. I assume that internally, the compiler is somehow promoting foo to be an owned type, or creating a hidden temporary which owns the Foo. It would be nice if the same courtesy could be afforded to more mutable references until something actually tries to violate constraints, like actually modifying the reference, rather than just preventing anonymous mutable borrows entirely). The big place I've seen this be a problem in real life is with the builder pattern; if I want to populate my builder with some arguments, and then build from it more than once, I need three statements: to assign the un-populated builder, to populate it, and to build it, where I'd really like two: to assign the populated builder, and to build it.
There is work underway in the language to solve part of this problem, with something called non-lexical lifetimes (NLL). Take a look at call_four, call_five, and call_six. Again, these look like they should be equivalent, but call_four makes the borrow checker unhappy.
This time, the borrow checker is unhappy for the opposite reason: a variable's lifetime is bounded by its scope, and its scope continues until the end of the block in which it was declared, so baz, which mutably borrows f, precludes bar from being able to borrow it, even though baz could be analysed by the compiler to be dead. You can work around this by avoiding the assignment, as in call_five, or by introducing an inner scope, after which baz gets dropped, as in call_six. The NLL work will address this, and perform the liveness analysis to render these workarounds unnecessary.
Having to re-structure code which otherwise feels like it should be equivalent to satisfy the borrow checker is pretty frustrating, and occasionally means sacrificing clarity. It's great to see work being done to improve this situation, and I hope it continues to go further!
#[derive(Debug)]
struct Foo {}
impl Foo {
fn foo(&self) -> &Foo {
self
}
fn bar(&mut self) -> &Foo {
self
}
fn baz(&mut self) -> &mut Foo {
self
}
}
fn call_one() {
let bar = Foo{}.bar();
println!("{:?}", bar);
}
fn call_two() {
let mut foo = Foo{};
let bar = foo.bar();
println!("{:?}", bar);
}
fn call_three() {
let foo = Foo{}.foo();
println!("{:?}", foo);
}
fn call_four() {
let mut f = Foo{};
let baz = f.baz();
println!("{:?}", baz);
let bar = f.bar();
println!("{:?}", bar);
}
fn call_five() {
let mut f = Foo{};
println!("{:?}", f.baz());
println!("{:?}", f.bar());
}
fn call_six() {
let mut f = Foo{};
{
let baz = f.baz();
println!("{:?}", baz);
}
let bar = f.bar();
println!("{:?}", bar);
}
First, let's look at call_one and call_two. These look like they should be identical code; the simple application of an "inline" refactoring (or "extract variable", if going in the other direction). But in reality, call_two is fine, but the borrow checker is unhappy with call_one:
error[E0597]: borrowed value does not live long enough
--> src/main.rs:19:26
|
19 | let bar = Foo{}.bar();
| ----- ^ temporary value dropped here while still borrowed
| |
| temporary value created here
20 | println!("{:?}", bar);
21 | }
| - temporary value needs to live until here
|
= note: consider using a `let` binding to increase its lifetime
So what's going on here? When you declare a variable with let, its lifetime begins, and it remains live until the variable it's bound to goes out of scope. But if you never bind an expression to a variable with let, its lifetime immediately ends, and it gets dropped. There seem to be some special-cases where this is transparently handled for you (look at call_three - it's exactly the same as call_one which the borrow checker finds problematic, but the borrow checker is fine with this one. The only difference is that bar borrows self mutably, whereas foo doesn't. I assume that internally, the compiler is somehow promoting foo to be an owned type, or creating a hidden temporary which owns the Foo. It would be nice if the same courtesy could be afforded to more mutable references until something actually tries to violate constraints, like actually modifying the reference, rather than just preventing anonymous mutable borrows entirely). The big place I've seen this be a problem in real life is with the builder pattern; if I want to populate my builder with some arguments, and then build from it more than once, I need three statements: to assign the un-populated builder, to populate it, and to build it, where I'd really like two: to assign the populated builder, and to build it.
There is work underway in the language to solve part of this problem, with something called non-lexical lifetimes (NLL). Take a look at call_four, call_five, and call_six. Again, these look like they should be equivalent, but call_four makes the borrow checker unhappy.
error[E0499]: cannot borrow `f` as mutable more than once at a time
--> src/main.rs:39:15
|
37 | let baz = f.baz();
| - first mutable borrow occurs here
38 | println!("{:?}", baz);
39 | let bar = f.bar();
| ^ second mutable borrow occurs here
40 | println!("{:?}", bar);
41 | }
| - first borrow ends here
Having to re-structure code which otherwise feels like it should be equivalent to satisfy the borrow checker is pretty frustrating, and occasionally means sacrificing clarity. It's great to see work being done to improve this situation, and I hope it continues to go further!
Pixel 2
For the last 6ish years, I’ve primarily been an iPhone user. Every few years, I’ve tried out a flagship Android phone as my main phone for a non-trivial period of time (most recently the Nexus 6), and I’ve always switched back to an iPhone. About six weeks ago, I got a Pixel 2, and I’ve been using it pretty much exclusively. Here are my thoughts on being both an Android user, and a Pixel 2 user.
tl;dr: Android is pretty good - definitely up to iOS quality. The hardware is pretty solid, but lacks a lot of finesse. If I switch back to an iPhone, it will be because of the physical build quality, but I’m not likely to do that at least until this phone dies.
Android
Android has gotten a lot better. The user experience is a lot more consistent and a lot less flaky than it used to be. I’ve only had Google Play Services crash once. The permissions model is a lot cleaner (you can deny specific permissions to specific apps, though more on that later). In the past, when I’ve switched from an iPhone to an Android phone, I’ve instantly felt painful regressions, and they wouldn’t go away no matter how much I got used to the operating system. This time, while there are things I prefer about each system, I can’t obviously point at one and say “I strongly prefer this one”. I don’t really have any major complaints about the core Android operating system any more. But there are some things I really like about it:
- ChromeView integration. If I open a link in the Twitter app, I can select “Open in Chrome” from the drop-down menu, and my exact screen will be preserved, scroll-location and all, just in a standalone Chrome tab. This is amazing.
- Chrome tabs are preserved offline a lot more than I saw on my iPhone, and the “download when next online” feature is pretty swanky too.
- Gboard (Google’s swipe keyboard) is really nice (and because it’s better system-wide supported, works in most apps, unlike on iOS). Nonetheless, every now and then an app appears to use its own custom keyboard, or something, and I find that my attempts to swipe to type fail randomly.
- Sharing intents work a lot nicer than iOS’s half-baked copy of them.
I still don’t understand why Phone and Contacts are separate apps, or when I’m meant to use each of them.
Region-locked app-store
My one big complaint, which I’m aware is a little niche, is around region locking in the Play store. For weird historical reasons, my Google account is US-based rather than UK-based. Accordingly, I cannot install the apps for one of my banks, or one of my credit cards, and the BBC iPlayer and iPlayer Radio apps don’t let me download content. On iOS, I could switch the country in which my account is registered through an official process, by entering a billing address and card in that country, which would let me download apps from the other country, and then switch back. On Android, I can only emulate this by signing in as a second user, which is a generally poorly supported flow. For one thing, it means I need to switch accounts to get to the apps, and for another, storage doesn’t really work properly (downloads from the BBC apps fail in obscure ways, for instance).
The workaround here would be to side-load the apps, but that’s kind of sketchy security-wise.
If you want to install apps from multiple regions, I recommend an iPhone.
Permissions
- “All files and photos”. Oh god. If you want to give an app permission to save any files or photos or data, you need to give it permission to read, write, or delete all files and/or photos on your phone. This is very alarming! Please please please let apps have a private sandbox, and a separate permission for some kind of global storage!
- Notifications are on by default. Accordingly, my notifications are super spammy, until I manually tweak them down. This is pretty annoying; I suspect that one of the reasons that iOS is less spammy notification-wise is that there’s a user prompt before notifications can ever be sent, and so app developers need to convince people to enable notifications with some good reason before they can spam.
- Each bank / credit card app on my phone told me it needed permission to make and manage phone calls, and promised me it wouldn’t ever actually use it, but it needed to use it to verify security somehow. Weird.
Photos
The Photos app doesn’t appear to have a way to let me scroll through all of my taken photos, received photos from WhatsApp, received photos from each other app, screenshots, etc, in one view; I need to remember which one of the above the photo I’m looking for was, and open the correct folder. Not the end of the world, but a little annoying.
Android Pay
Android Pay appears to have less broad adoption from credit card companies and banks than Apple Pay does. But I don’t really use either, so it doesn’t matter to me that much.
WiFi Connections
My iPhone was pretty sluggish to connect to wifi, and often needed prompting, particularly when on the tube. My Pixel 2 appears to join wifi networks really quickly, and without prompting, which gives a much smoother experience on the tube.
Automatic re-enabling of Wifi
I found out about this feature yesterday! If you’ve disabled wifi, and Android detects that you’re near a wifi network which it thinks is good, it will turn your wifi back on! This is a terrible feature to enable by default! Firstly, if I’ve disabled my wifi, it’s for a reason, and you shouldn’t assume to know better. Secondly, I can only assume that it does this detection by probing, which means that 1) it’s using my battery to do so, and 2) it’s broadcasting my MAC address when I may have asked it not to.
The back button
I find it pretty hard to predict what the back button will do at any time. If I open a link from an email, and press back, it closes the Chrome tab and takes me back to my email. If I click a Twitter link from Signal, and press back, it takes me to the most recent Tweet before that which I opened from a link in Signal. What?
Camera
The camera is pretty good. A similar quality to what I’d expect on an iPhone (though slightly worse in low-light conditions). It takes “motion images” by default, which are kind of fun (they give a 1-3 second animation around the frame you’re actually taking), but a little annoying to share (if I try to email them to myself, they’re about 8MB each, and there doesn’t appear to be an easy way to just send one frame).
Pixel 2 software
This is something I’m actually really disappointed about. One of the joys of getting a Google experience phone is that vendors don’t mess with the UI, bundle in un-removable apps, etc. And yet, on my homescreen I cannot move or remove the search bar at the bottom of the screen (which I never use, but frequently accidentally tap on when I’m trying to open one of my pinned apps), and I cannot move or remove the “At a glance” display at the top of the screen. I really wish I could actually configure my device as I want to, without rooting it.
Pixel 2 hardware
Weirdly, this is my area where I have the most complaints. Which I wasn’t expecting, considering the price tag and marketing of the phone.
Screen
My screen is covered in scratches. I have no idea what from; I’ve not had any incidents which should have scratched it. I’ve used it exactly the same as my iPhone; keeping it in the same pocket with the same things, putting it on my same bed-side table. I can only assume the iPhone screen is made of a tougher material, or has a coating, or something. I’m actually pretty disappointed that my phone has several noticeable scratches six weeks in.
Fingerprint sensor
I really dislike the location of the fingerprint sensor. It’s on the back, just above where your index finger rests on the back of the phone. With my iPhone, if I had the phone on my desk, I could tap the sensor with my index finger, and interact with my phone. With my Pixel 2, I need to pick up the phone, and poke the back of it; this is a surprisingly much higher friction activity to do something like glance at a message I’ve received.
I also accidentally press the fingerprint sensor a lot when my phone is in my pocket when fumbling for my phone, or wallet, or whatever, which necessitates putting in my PIN to unlock (because it hits the fingerprint attempt limit). Minor, but annoying.
Speaker location
At night, I leave my phone face-down because it has a flashy light on it. The speaker appears to be on the front of the phone. So if I want to settle down with a podcast or music or something, I can’t really hear it. My iPhone didn’t suffer from this problem, as the speaker is at the bottom.
One USB-C port is awful
My biggest wish is that my phone had either a 3.5mm headphone jack, or two USB-C ports, so that I can plug in both a charger and headphones. I have some good bluetooth headphones, but sometimes I want to use cheap earphones, so I’ve bought a bluetooth receiver (which is excellent, for the record) that I can plug my earphones into, but I need to carry it around, and charge it, and just ugh.
I looked at getting a splitter which allows you to plug both headphones and power into one end, and plugs into the USB-C port on the phone. As far as I can tell, there are several available, but most of them probably don’t work with any particular phone. Ugh.
The bundled USB-C -> 3.5mm adapter is awful
You need to jiggle it around before it actually works. And there’s no indication on the phone anywhere that headphones are plugged in, so reliably I will start playing music out of the phone speaker, and then keep jiggling the adapter until it plays through the earphones. This is simply not acceptable.
USB-C is far from ubiquitous
I bought a set of USB-C - USB-A cables, so that I can plug my phone into existing chargers, or my laptop, or whatever, to charge. Bundling one of these would have been nice.
Size
The phone is big. Bigger than I’d like. I can just about use it with one hand (I have pretty big hands). But most annoying, big without it being for the screen. About 1.3cm are added at both the top and bottom around the screen for… Black plastic. Seems annoying. I’d love my phone to just be 2.5cm shorter.
Sharing by Bluetooth
I miss Airdrop. I’ve tried sharing images over bluetooth from my phone several times, both to other phones and computers. It’s failed 100% of the time. I just email stuff now, but I’d rather not have to.