Google I/O 2011: Building Aggressively Compatible Android Games

Pruett: Hi, folks.

So, this is Building AggressivelyCompatible Android Games.

And you're allin the right spot.


Great.

Nobody got [indistinct].

Hi.

My name is Chris.

I am the CEO of a companycalled Robot Invader, which isa mobile game developer for Androidand other mobile platforms.

And actually, up until about a month ago, I was a Google employee.

I workedon the Android team as a developer advocatefor game developers.

My role was basically to goto game developers and say, hey, you should makea cool game for Android, and here, here's how to do it.

I'll help you do it.

If you've been to previous IOs, you might have seen me or if you ever, you know, watch IO sessions on YouTube, I've spoke a couple of timesabout a game that I built while at Googlecalled Replica Island.

This talk is not aboutReplica Island.

This talk is about games that are aggressively compatibleacross devices.

But I think that Replica Islandfits that description and so I'm going to reference itin my talk and try to, you know, use itas a case study of how a game can runon everything.

So like I said, the topic todayis device diversity and we all knowthat there are a lot of Android devicesout there, and guess what? The common factor is that on every single oneof those devices, every single oneof those devices is owned by somebody who wouldlike to play video games.

And they would probablylike to pay you money to play those video games, and that's why you should beinterested in this.

Replica Island, I released about a year ago, it's getting close to 2 millioninstalls at this point.

We're not quite there yet.

But it's doing pretty well.

The ratingsare pretty good.

And I think that, um, part of the reason that it's beenas successful as it is is that it runson all of these phones.

You know, we've gotover here on one side, we got the G1.

That's pretty muchthe minimum spec.

I put the Nexus Sas the high end, although they arevery quickly phones that are coming outthat are faster than that.

But that's the range, right?It runs on all those phones.

It doesn't just run onthose phones, actually.

It runs onall these phones too.

I tried to get this slideto go faster, and this is the maximum speed that KeyNote would allow meto show these phones to you.

There's a lot of them.

There's like, 160 or something, the last time I checked.

And actually, that data pointis probably three months old, which is an eonin this industry.

It runs onall these devices.

And actually, this isnot an exhaustive list.

There's quite a number ofAndroid phones out there today that Replica Islandworks fine on.

Oh, yeah.

It runs on tabletsand TVs and stuff too.

I didn't really plan this.

I, of course, I wantedthe game to run on everything but when I wrote the game, you know, it was designed for a G1.

That was the platformthat I was building it on.

It just turned out that I madesome correct assumptions and because I madethose correct assumptions, it was very easy to runon all devices and in a couple of cases, extend what I'd already written to work on all devices.

So today I'll tell you aboutwhat those assumptions were and how you can replicatethat on your own games.

There's also a bunch of stuffthat I did for Replica Island that isn't relevantany longer.

For example, I spent a long timeon Java optimization.

Java optimizationmatters a whole lot if you are targeting a G1.

It doesn't matter very muchanymore for any other device, and there's two reasonsfor that.

One, Java has gottena whole lot faster with different versionsof Android, and number two, the phones havegotten a whole lot faster.

Actually, I'll saythree reasons.

Number three is, how many game developers do we actually havein the room here who are writing games right now? Okay.

Okay.

That's a pretty good number.

How many of youare writing them in Java? How manyare using Dalvik in the Java languageto build your games? Yeah, that's right.

How many of you are usingC++ and NDK, right? Okay.

So, that's why Java optimizationdoesn't matter any longer.

And I'm not gonnatalk about it.

Dalvik is really fast.

If you want to do that, you can.

I'll skip it in this talk.

Before we start talking aboutdiverse diversity, you need to understand how the Android compatibilityprogram works.

So, let's do anothershow of hands.

How many peopleactually knew that Android hasa compatibility program? Okay.

That's not bad.

What? 30%, maybe? Yeah, Android hasa compatibility program.

And if you want to buildan Android device that's considered compatible, you have to pay attention to it.

This is an open sourceprogram.

You can go check the spec out, you know, online.

It's compatibility.

Android.

com and you can read throughthe whole spec.

And any Android phone that'sgonna be considered compatible has to meet that spec.

It's pretty detailed too.

I mean, it givesa device manufacturer areas where they can makemodifications.

Like, say they want to havea different sized screen.

Well, there's a rangeof screen sizes that you can support and still be consideredAndroid compatible, but it goes intoa lot of detail about what you canand cannot do.

And the purpose of this specis to require that peoplewho are building phones to build phones that aregonna run applications for Android Marketthat are written correctly.

So, remember, I had mentioned, you know, all these phones back here? Well, guess what?They're all compatible.

Every single one of these has met the Androidcompatibility spec.

Well, how do weenforce that? Right? How does–how do you knowif a device has passed this arbitraryopen source spec? Well, Google providessomething called CTS, which is a CompatibilityTest Suite.

This is about20, 000 plus tests that a device manufacturercan download.

It's also open source, can run on their device and get output from.

And that output basically says, yes, I passed, or no, I did not.

And if there were failures, it says, well, you screwed this up, and you screwed this up, and you screwed this up.

And if the OEM doesn't go backand correct those failures, then there's no chance for themto access Google services like Android Marketand GMAIL and Maps.

These services are not partof the Android open source.

They are given to OEMs on thecondition that they pass CTS.

So, if you don't havea compatible device, you don't have any of theseinstalled.

Another way to think about thatfrom a developer's perspective is that you're usingAndroid Market as your distribution platform, you're guaranteed that you'reonly going to be distributing to devices that have passedGoogle's 20, 000 tests that are requiredfor compatibility.

So, that's the basis.

That's the bedrock stuffyou need to know before thinking abouthow a single binary can be compatible withall these different devices.

I'm gonna saythere's three steps to aggressive compatibility.

The first step ischeck your assumptions.

I worked witha lot of developers in my role at Google who hadtrouble with compatibility, and it wasn't becausethey wrote bad code and it wasn't becausethe devices were broken.

It's because they originallywrote that code for some other platform, and they made a bunchof assumptions that were specificto that other platform.

And when they ported the codeover to Android, they didn't changethose assumptions, and those assumptionsturned out not to be the same set of assumptions thatyou have to make on Android.

So, the first point here ischeck your assumptions.

The second isfollow the rules.

We'll talk aboutwhat these rules are in a little bit of detail.

But the main message hereis there are rules that you must maintainto be compatible, and if you break them you will crashon certain devices.

And the third rule ismanage your spec.

I'll get into a lot of detailabout that.

But basically, you can tellAndroid Market what kind of requirementsyour application has.

There's a secret fourth rulethat's specific to video games, and I'll talk about it later, but I'm not gonna tell youwhat it is right now.

So, let's talk aboutassumptions.

There's a lot of weird hardwareout there, right? I mean, there's weird hardwareout there right now.

And the thing is, Android devices are coming out really, really, really fast.

So you can be guaranteedthat whatever is normal today, or whatever is weird today, will be normal tomorrow and something weirderwill have shipped.

And I had that idea in mind when I was working onReplica Island.

I didn't knowwhat was gonna ship, but I knew that it wasn'tgonna be exactly the same as what had already shipped.

And I made fourbasic assumptions that sort of future-proofed myapplication for compatibility.

You have to understandthat I shipped Replica Island, I want to sayjust over a year ago, and most of those phoneson that mega slide were not out yet.

Most of those phones shippedafter I released Replica Island.

And without me doing anything, they are still compatible.

So, I'm gonna chalk that up tothese sort of basic assumptions that I made.

First assumption–there'sno standard for screen size.

Your screen can be any sizeand it can be any resolution.

Now, there are requirementsin the spec that I mentioned, the compatibility spec, that require physical sizeand density, so there's a range that you'regonna be working within, but you can't be guaranteedthat, say, all of your devicesare gonna match 480×320.

That's what the G1 is.

Nor can you be guaranteedthat all devices will match 800×480, or something like that.

There's basicallyno standard at all.

And so you need to dealwith that.

And the best wayto deal with it is to allow your graphicsto scale.

If you're using OpenGL and if you've attended my otherIO sessions in the past– you should be– it's very easyto scale your graphics.

Very often, it can come downto a GL viewport call.

In my case, I actually changethe GL viewport, and I also scale the contentof my scene a little bit to match the aspect ratioof the display.

You can see here that atthe 480×320 version of the game, you can see a little bitless to the right than the 800×480 version.

A more dramatic example isthe QVGA versus FYVGA versus the game.

Same game, same graphics, but what I do is I look at the sizeof the screen at runtime.

I have a fixed heightthat I want to align to that I've fixedfor game design purposes.

It's basically 480 pixels tallbecause that's what the G1 had.

I scale everything upto match that height and then I allow the leftand right sides of the screen to grow or stretchor stretch or contract.

And for my particulargame design, that's completely acceptable.

Allows the game to runon any device.

If I'd been using, if I'd madea 3D game that has perspective, this would have beena lot simpler.

I could just change the viewportto match the display, no problem, just a matrix, no big deal.

But if I wasn't using OpenGL, the actual Android layout system has all kinds of toolsfor scaling graphics and moving things aroundand adjusting them.

But I think probably most peoplein the room here are using OpenGL.

Key message is notthat it's hard but you have to think about itbefore you write your game.

The other thingthat I did correctly but I would do better next time is I shipped one set ofresolution of graphics and I scale up.

And the resolutionthat I shipped was HVGA.

HVGA scaled up on a wideVGA display like a Nexus One or Galaxy S.

It looks okay.

The space, still pretty smallso I can get away with it.

When you run Replica Islandon a zoom, you'll see it looksa little bit pixilated and that's becausepretty low resolution art is being scaled up to apretty large resolution display.

So if I were smarterwhen I built this game, what I would have done is madehigher resolution graphics and scaled downon smaller screens.

Ship with, say, zoom compliant graphics and then plan to scaleeverything down, you know, when you run on a G1 orwhen you run on the Nexus One.

That way, scaling downalways looks good.

Scaling up, not so much.

Second major assumptionthat I made was that the G1 wasgoing to be the minimum spec.

That turned out to bepretty true.

There are two devicesin the world that I know to be slowerthan the G1 for games.

Those are the HTC Tattooand HTC Wildfire, and those devices are slowerbecause they do not have a GPU.

Game still runson those devices because Android providesa software renderer for OpenGL ES1.

0 which is what you useif you use the emulator.

But as you know, if you use the emulator, it's a little bit slow.

So, game is playableon those devices.

It's a little bit slowerthan I'd like, but you know, no big deal.

For all intents and purposes, the G1 is the minimum spec.

For the vast majority of devicesout there, it doesn't get any slowerthan that.

I assumed that OpenGL, yes, was going to be the fastest way across all devicesto draw 2D content.

This turned out to be true, but it's a subtle point.

It's a subtle point becausethe way to draw fast 2D graphics on a Nexus One might actually beto use the CPU.

That's because the Nexus One CPUis really, really fast.

It's in fact so fastthat it can outrun its GPU for doing 2D blitz.

There are actuallya lot of devices that have really fast CPUsthat you can do this on.

But it's onlythe high-end devices.

I say the Nexus One, probably the Galaxy S, those are about the two thatI've tested, the Nexus S.

Right? There are devices out therethat have real fast CPUs and can–you can renderthe same thing in software faster than you canrender in hardware.

But the reason to useOpenGL ES is that you want to becompatible with every device, not just those superfastCPU devices.

And generally, the GPUis the way to go.

It is the fastest pathfor rendering 2D concept.

I wrote this applicationcalled Sprite Method Test, which is open source.

You can search for it if youwant to test this stuff out.

But it lets you draw thesame scene with CPU and GPU and, you know, see what the differences are.

I also–this ismy fourth assumption.

I assumed that all the devices were gonna kind of belike the G1.

I knew that they weregonna change and be faster and have different sizedscreens, but I assumed that they weregonna have a trackball and that they were gonnahave single touch displays like the G1 and that they mighthave a keyboard or not.

You know, that was totallyfalse, as we know now, right? There's a whole lot ofdifferent phones out there.

And they've got a lot ofdifferent interfaces that people are gonnaplay with.

Some of them like the Nexus Oneactually do have a trackball.

Some of themhave keyboards.

Some of them have little dinkyD-Pads on those keyboards.

Some have really nicemultifinger multitouch.

Some of them are weird, likethis device up in the top left.

That has a track padon the back of the display that the Xperia Playof course, has real game controls, a D-Pad and buttons.

And then there are devices–so far only one that I know of– like the original Xperia which are problematicfor game developers because it doesn't havereally any hardware buttons except for backand menu and home.

It doesn't supportmultitouch.

So, where do you putyour game controls? If you have a game you can playwith a single finger, you're all right.

But if you are–if you're like my game, which is–requires at leastmotion and jump, you know, how do you deal with that? Well, a solution for me was toprovide customizable controls.

This is a solutionI did not come to until after I'd shipped the game'cause like I said, a lot of those devices came outafter I shipped Replica Island.

But in all of the updatesI've made since shipping have been to add morecustomizable controls.

So now if you downloadReplica Island and go into the options, you can set up how you wantto control the game with a lot of granularity.

Breaks down intofour major spots.

There's the trackball support, which is the original versionof the controls that I wrote.

There's keyboard supportwhich gives me access to things like the Xperia Playwithout any changes.

Virtual pad which are for just buttons on the screenbut requires multitouch.

And Tilt for devices likethe original Xperia that only have single touch.

And between these four, I pretty muchseem to have covered all of the devicesin the world.

Like I said, as far as I know, all devices out there that are compatible and haveAndroid Market installed you can play this game on.

The original control schemethat I came up with was designed around the idea that some displaysare gonna be single touch.

So I had the trackballfor motion and I have these two mutuallyexclusive buttons on the screen for jump and attack, and that way, you never have tohave two fingers on the screen.

But nowadays, you know, now we know that there'sa lot more devices out there that have all kinds ofmultitouch points.

So it's probably importantto talk a little bit about the different typesof multitouch screens that are out there.

Multitouch screens basicallycome in three flavors.

There's single touch.

Right? Multitouch.

And then there's what I callcrappy multitouch.

And crappy multitouchis what my best friend, the Nexus One, has.

Crappy multitouchis a multitouch display that can–that can detecttwo fingers at the same time.

But when those pointersare moving, it can swap the componentsof one pointer for the other in a very specific case.

And that case iswhen the two pointers cross the same horizontalor vertical axis.

So the caonical problem casefor this type of display is if you have a fingeron one side of the display and another finger onthe other side of the display and you drag them across, at that vertical linewhere they pass you may getcomponent swapping.

So, your X from pointer onemay suddenly become your X from pointer two.

And if you're drawing a lineon the display where those fingers are, you'll see them go like this, which is really frustratingfor game developers because if you want to havea dual stick control scheme– which I recommendyou not do anyway– but if you wanted to havea dual stick control scheme, you know, that's gonna betwo fingers that are passingthe same horizontal axis, and you're gonna getcomponent swapping, and it's gonna bevery difficult to control.

So you can still deal withcrappy multitouch displays.

My solution was just to get ridof all vertical motion in the fingers.

.

.

you know, I have a slider that moves left and rightwith one finger and buttonson the other side.

And there'sthis horizontal line that they are both on, but they never move verticallyto cross it.

And so, this actually workspretty well on the Nexus One.

In fact, when I showed itto some colleagues who were aware of the crappymultitouch problem, they were impressed that I was actually ableto get this to work.

They thought I'd done somecomplicated heuristic, you know, to unswap my controlpoints or something.

Nothing like that.

Just made sure that there wasno vertical motion.

Couple of other tricksrelated to screens.

The back and menu keyson a lot of devices like, again, my friend, the Nexus One, are flush with the display.

And that meansif you have buttons on the right sideof the display or you're gonna usea trackball for control, it's pretty easyto slip off the track pad and hit the back or menubuttons.

And that's a pretty baduser interface because usually those buttonstake you out of the game.

So, for Replica Island, I ignore those buttons if they happen to occurwithin 400 milliseconds of a game event.

A game event is the usertouched the screen or the user touchedthe trackball.

So that means if the user'stouching the screen and they slide offand they hit the back button, nothing is gonna happen, 'cause I'm gonna ignorethat back button.

But if they picktheir finger up and they put it back downon the back button, that actually takes longerthan 400 milliseconds.

So the back when we read it, it'll be fine.

There's no mis-clicking.

You should also be awarethat these devices have really small buses, right? The memory bandwidthis not good.

That means loading your textureto VRAM takes time, and you probably can't do itat runtime.

One solution to that is to usetexture compression.

There's about four typesof texture compression in the worldout there today.

There's ATITC, whichis proprietary to ATI devices– Snap Dragon, okay, things like that, stuff that Qualcomm makesmostly.

There's PVRTC, which isproprietary to power VR devices like the Droid.

There is DXT which you'll findin video devices like the Zoom, also proprietary.

There's ETC1.

ETC1 is a non-proprietary formatthat's supported by all devices that support OpenGL ES2.

0.

So it's great.

You can use it.

Its fatal flaw isthat it doesn't support Alpha.

You can't havean Alpha Channel or do any sort of transparencywith ETC1.

So, some developers arereally tricky.

Right? Some developers have shown methat they take their Colormap and they take their Alpha mapand they save it as two different texturesand they put them back together in the shader.

Solved.

Okay.

You know, or you canchoose to do what I did which is just not compressyour textures.

I was actually able to fitthe whole game uncompressed in VRAM, so didn'thave to worry about it.

But if you are not like me, and you're worried about fitting into VRAMon all devices, you should be aware that texturecompression is device specific.

Now, there is a little solutionthat's better than ETC1 that we'll talk aboutin a little bit.

But you should know thatif you do decide to use ETC1, OpenGL ES represents a 2.

0, which is what is required.

It represents the vast majorityof devices out there, over–greater than 70%.

There's some question markson this slide because the parsing tool we usedto generate this data, like, totally failed.

But even if we assignedOpenGL ES1.

0 to that question mark part, we could see that the vastmajority of devices are 2.

0 capable.

So if you want to rely onsomething like ETC1, you can safely do it.

Same goes forOpenGL extensions.

Extensions are a system by which the OpenGL speccan be extended.

They're optional.

Some hardware's going tosupport them, some is not.

There's a GL extension stringwhich contains at runtime which extensionsa current device supports, and you can query that.

You should definitely do itbefore using any extensions, otherwise you maycrash on a device that doesn't support theextensions you want to use.

I used two in Replica Island, Draw Textureand Vertex Buffer Objects.

Both of those are optional, and support for themseems to be universal.

But I still check for themjust in case.

So, let's move onto rules.

We talked about assumptionsyou need to–you can make, and those assumptionsthat you cannot make to build a compatible game.

Now, what are the rules? What are the borderline thingsyou should totally not do? Probably, at least threeother people at Google IO have talked about thisbecause it ended up affecting a lot of appswhen the Zoom came out.

But if you didn't know, it turns out that devices havedifferent default orientations.

If you're holding a tablet, its default orientationis landscape, and if you have a phone orsomething like the Galaxy Tab, its default orientationis portrait.

And you're like, like, why do I care, right? Because when yourun your game, you're gonna justset the orientation to whatever you want, and that's what's gonna display.

Well, you care becauseaccelerometer data that you getout of the hardware is relative to the defaultorientation of the device.

So if you have any sort oftilt or orientation controls in your game, and your methodis just to suck those out of the hardwareand say, okay, it looks like Y decreased– that means the useris tilting left– you're gonna besorely disappointed when you run on a device that has a differentdefault orientation because your controlswill all be 90 degrees off.

This is really easy to fixonce you know about it.

In fact, NVIDIAhas provided a useful function to just convert between the device specificaccelerometer data and a canonical screen spaceaccelerometer version.

And this is my sort ofJava language version of their function.

But if you check out theirTegra Zone developer area on their website, you can find the originalC++ version.

I just dropped thisinto Replica Island, and it solved all my problems.

It was great.

Another rule–if you're using JNI, which isthe Java Native Interface, that's the system by which youcall from some other language, like the Java language, through Dalvikinto native code.

If you're using it, you need to be careful because it's a little fragile.

In particular, the JNIEnv variable, which comes downwith every callback, you can't cache that.

It changes every time, or it could potentially change every time you makethat callback.

And it could changeif the callback is called from a different thread.

And just for a lot of fun, if you cache it, and if you use the wrongenvironment variable in the wrong context, you will get extremely difficultto debug crashes, and they won't be universal.

They'll be things likehorrible race conditions that work on some devicesand not others.

Don't ever cachethis variable.

I worked with onevery well known developer who had releaseda popular Android game, and it workedon all devices except for one.

And the developerwas positive that that device was brokenbecause.

.

.

it just didn't workon that thing.

Everything else worked.

Just the one device.

.

.

didn't work on.

And we debugged it together and when we gotto the bottom of it, it turned out that he wascaching this variable and on that particular device, there's a race condition where, you know, thread one wonbefore thread two and it caused a crash.

On every other device, it happened to be thread twowon before thread one.

By not caching this variableany longer and just passing it throughwhen and he got a callback, problems went away.

I think this is old newsfor most people here, so I won't spendtoo long on it.

But there's differentversions of Android.

Each new version of Androidintroduces new APIs.

If you're going to bebackwards compatible across a bunch of differentversions of Android, you need to only call APIsthat are available in your minimum version.

So you know, you gointo AndroidManifest and you say, oh, I have min SDK S3.

That means I can run onAndroid 1.

5 or higher.

But I compiled against eight, which is Android 2.

2, I believe.

So what happensif you want to call a function that was addedin Android 2.

2? Well, if you just call it, it'll compile okay.

But when you run on the G1or that other 1.

5 device, it's gonna crash.

If you use Dalvik reflection–or you could even branch on the build IDor the version ID– just be careful not to callmethods that may not exist in your minimum supportedsystem version.

And actually, the docsare really useful for this because they will show youexactly how– exactly which version every functionin the Android API was added.

Another rule.

You should be frugalwith your RAM use.

If you are writingto Dalvik, you have a fixed size heapand you know what it's gonna be.

On a minimum spec, it's gonna be 16 megabytes and on newer devices, it's much larger than that.

So you can deal withthat memory stuff pretty easily.

If you are allocatingfrom native code, which most of you said you are, there is no fixed size heap.

You can allocate as much memoryas you'd like until the device runs outof RAM, which is great, right? Because you're notlimited to 16 megabytes.

But it also means that if yourapplication needs 100 megabytes to run at its high watermarkand your user is on a phone that doesn't quite havethat much free, you may run intoan out-of-memory situation that you didn't expect.

So the message here is befrugal and fail gracefully.

Try to fit into as littleruntime memory as possible.

For reference, Replica Islandruns at 6 megs.

Keep your applicationas small as possible.

Older phones, especially the G1, but some other older phones have very littleinternal flash.

You should also absolutelysupport the apps to SD configurationin your Android manifest because that'll let peopleto move the application to the SD card.

You don't want to benon-compatible with a device just because you just don't haveenough space to install it.

If you are coding in C++, you may have consideredusing Neon.

Neon is a special architectureinstruction set that allows the optimization of certainfloating point operations.

Now, Neon is only supportedon devices that have RMV7 chipsets.

But the trick isnot all RMV7 chipsets support Neon.

So if you assumed that bybuilding against RMV7 you are guaranteed Neon support, you are wrong.

For example, the Zoomdoes not support Neon.

Now, there is a librarythat comes with the NDK called CPU Featureswhich you can use to check at runtime, whether or not this particularinstruction set is available.

But do that.

Make sure you check.

Otherwise, it will crashon the device [indistinct].

And, you know, I thinkeverybody here knows this because we pounded it intotheir heads at every Google IO, but don't call ondocumented code.

Don't use private APIs.

You can go into the Androidopen source tree and you can find a bunchof interesting-looking functions and you can find tricky waysto call them from your code, and if you do that, you guarantee that your gamewill crash at least– if not now, then in the future, when those APIs change.

APIs that are privateare private because they'relikely to change.

They're not readyfor primetime.

And if you rely upon them, you will crash.

So the main message hereis lots of diversity.

Right? I mean, there's lots of rules.

There's lots of flexibility and this is probablythe point in the lecture where everybody in the roomis like, oh, my God.

How am I gonna deal with this? This sounds horrible.

Like, for example, maybe you have some weirdocontrol scheme that only workson a subset of devices.

Right? Like, maybe you requirediscreet multitouch which is the non-crappymultitouch.

Or more likely, maybe you just want to use OpenGL ES2.

0because you want to write your whole graphics back endin shaders, and there's no way that's evergonna work on a device that doesn't supportOpenGL ES2.

0.

What are you gonna do, right? That's the segueinto our next talk, and the segue itself is thatMarket has a solution for you and it's calledAndroidManifest.

xml.

So, what isAndroidManifest.

xml? I mean, everybody fills it outwhen you build your application.

You put your app name in there and you describe your activitiesand stuff.

And it's metadataabout your application, but what is it really? As far as Market is concerned, it's your minimum spec.

For example, say we say, oh, I require this feature called Android.

Hardware.

Touchscreen.

Multitouch.

And I really do require it, 'cause I put the required fieldto true.

That means I cannot run– this application is notcompatible with devices that do not supportmultitouch.

And if you put thisin your manifest and you upload itto Android Market, Android Market will notdistribute it to devices that don't meetthat requirement.

So, your G1 users are noteven gonna be able to find your appin Android Market.

It's just not there.

If you use the web interface, find the app and try toinstall it to your G1, it'll say, oh, sorry.

Your phone is nothigh enough spec.

It doesn't meet the requirementsset by this application.

There's tons of stuff you canrequire in Android manifest.

This is why I call ita minimum spec.

There's all kinds ofrequirements you can put in here.

Here's a little taste.

Say you want toonly ship to phones that have auto focusing cameras.

Okay, you can do that.

Or say you want to shipto phones that support Wi-Fi.

You might think that all phonessupport Wi-Fi, but it's probablyan optional part of the spec.

So maybe if you require it, you won't be gettinguser reports from your users who are on the crazynon-Wi-Fi supporting device that comes out in six months.

Or here's a more commonrequirement for game developers.

Say, you do want to use oneof those proprietary texture formatsthat I talked about.

You can require it.

You can say, well, you know, I know I'm gonna cut outsome of my users but I want to usePVRTC.

Put this in your manifest and Market will not shipyour game to a device that can't support that versionof texture compression.

We talked aboutcrappy multitouch.

You can cut out crappymultitouch devices.

Say, I'm sorry, I have mydual sticks and I like it, and I don't want to even want todeal with you Nexus One users.

Whatever.

However manyother crappy multitouch devices might be out there.

Set this stuffto require it, and they will never see itat Market.

By the way, if you saidrequire to false here, it meansthat you want that feature but you can deal with itat runtime.

You can say, oh, well, I really wanted multitouch, but since you don't have it, here's another configuration you can use, some other way.

If you set it to required, Market will be real strict about how it filtersyour application.

Couple other examples.

We talked about requiringa minimum SDK version and a maximum SDK version.

You could also requirecombinations of different screen sizeand density.

That's physical size, physical density, and this isthe most common one.

If you want to requireOpenGL ES2.

0, that's that one at the bottom, you just say, well, I need an openOpenGL ES2.

0 and those old deviceswon't appear.

So by carefullymanaging your spec, you can effectivelycut out devices that you don't want to manage.

You know, we talked abouta lot of assumptions and a lot of ground rules, anda lot of them have to do with, well, there's a deviceover here that's this, and there's a deviceover here that's this.

And if you would like to say, you know, I don't even careabout this stuff over here, I only want to focuson these guys– AndroidManifest and AndroidMarket will let you do that.

If you think about this, this is actuallyreally powerful.

Because if we look atthe low end and the high end– what I'm gonna call the G1and the Zoom are probably the goodrepresentations of those two partsof the spectrum– that's a pretty big deltalike in terms of– let's just talk aboutperformance.

That's a pretty bigperformance delta.

Right? But let's say we requireOpenGL ES2.

0, say in our manifest.

Now we have to haveOpenGL ES2.

0.

Now the delta isa lot smaller.

Now the minimum specis the Droid, because that wasthe first device to ship withOpenGL ES2.

0.

Doesn't even matterif you use OpenGL.

Your whole thing might belike this 2D CPU game.

Who cares? If you say in manifest, in the manifest file, that you require OpenGL ES2.

0, it will cut out the low end.

You know, the caveatto this approach, right, is that every timeyou do this, you are cutting outsome number of users, right? The size of your user baseis getting smaller.

And of course, you know, in addition to writingawesome games, we'd probably all like toalso, you know, make moneyoff of those games.

So having more usersis always a good thing.

So it's in your interest to tryto be as compatible as possible.

But when there'san area of diversity that's just too open for you, you can put your foot downwith manifest and require somethingin the spec.

Here's the specfor Replica Island.

I don't really require anything.

I work on all screen sizes.

I run on everythingAndroid 1.

5 and higher.

Now because I'm able to supportsuch a wide variety of devices, you can see thatin my install information, I have more Android 1.

5 users, a lot more than what's normalfor this category.

I have 15%versus the standard, which is 4%.

And I believe that this isbecause if you are a.

.

.

user has a two-year-old device and it's only gotAndroid 1.

5 on it and you go to Android Marketlooking for some games to play, it's a little bit slim pickings, you know.

Like, most developershave moved on and said, well, I'm gonna requireAndroid 2.

0 or I'm gonna requireAndroid 1.

6.

But since I was able to supportthose users, that makes my app very visible, and so as a resultI have a lot of 1.

5 users.

It's up to you.

If you want tosupport everybody or you only want tosupport a subset, you can control thatwith AndroidManifest.

xml.

Okay, now, I want to talk aboutthe secret fourth rule.

This is specific to games.

This is a subtle pointthat I did not understand until after I shippedReplica Island.

It is true that these devicesare very different.

It's true that there's a lotof device diversity.

It's also truethat you can solve it.

There's Dalvik reflection, there's scaling your graphics, there's, you know, using the Android APIsto do automatic scaling for you or adding customizable controls, things like that.

Those are alltechnical problems.

You can solve them.

You sit down, write some more code.

It'll work.

Much harder problemis that your users are also gonna bea very diverse group.

It used to be– you know, my backgroundbefore I went to Google was writingconsole games.

So, I wrote a lot of gamesfor Game Boy Advance.

Game Boy Advance, you pretty much know who your target group is.

It's kids between the age of6 and 12.

Right? Or if you're gonna writean Xbox 360 game, you can bepretty much guaranteed that your target audienceis males between 15 and 30.

All right? Just by the market segment thatthe console itself controls.

But on a phone, I mean, everybody's got a phone.

Right? What do you knowabout your user? How much do you knowabout what they want or what kind of gamesthey want to play? You know very little.

I found outthat when I added all this customizationfor controls, what surprised me about itwas that users who didn't need tocustomize their controls, because I'd alreadyprovided a solution for them, were going inand customizing them anyway.

You know, I addedlike the tilt stuff in there for the basicallythe Xperia users.

And I kind of thinkthat that's a difficult control scheme to use.

But I had users telling methat they preferred tilt over the originaltrackball control scheme even though they hadlike an Nexus One or a G1.

That surprised me.

The user was different, not just the device.

You know, in designing Replica Island, I had some idea that usersare gonna want different things.

So in the game design, I tried to provide different aspectsof interestingness, different poles by whichpeople could, you know, become interestedin the game.

One of those thingswas just a game play, right? Some users are gonnalike crushing stuff and moving through the level and trying tofigure out the puzzle and making itto the next level.

That's the whole gamefor them.

That's enough.

Right? So try to make that part good.

Another aspect ofinterestingness was a story, right? Some people might not care very muchabout crushing stuff, but they want to find outwho they can trust or who's the real bad guy?What's the secret ending? You know, art stylewas another thing.

I tried to go for a retro16-bit game look even though the gameplays very differently than a retro game, because I thought that that would drawsome users in.

That's what they're gonna beinterested in.

And recently, I've actually added explicit difficultysettings for new users.

I had some dynamic difficultyadjustment in there before, but it turns out that the userbase is so wide here that I can makea much better game if the usertells me upfront what kind of gamethey'd like to play.

Some people want to puttheir pride on the line.

They want a challengeand they want to feel awesome when they've completedthat challenge.

And other people are like, you know what? I'm just gonna play thisfor five minutes.

I don't really wantto play the same level over and overand over again.

I just want tosort of glide through.

So by providing specificdifficulty levels, I was able to accessnot just more devices, but more users.

One thing I didn't have to dobut you should think about is I did not provide an option to customizethe graphics quality.

If you want to customize thegraphics quality in your game and you're using OpenGL, it should be really easy.

It's one call to theSurfaceHolder.

setFixedFunction.

Sorry.

setFixedSize method.

What that does isit makes your window that you're rendering to smallerthan the window of the display, and then the systemwill automatically scale it up.

So the effect isyou're filling fewer pixels, and actually fill is the partthat's slow on these devices.

So you're fillingfewer pixels, but it still takes upthe whole screen.

The graphicslook chunkier.

I don't knowif you can tell, but on the leftis native resolution and on the right is 50%reduced and scaled back up.

It does looka lot chunkier.

Although at 75%most users probably can't tell.

If you need to getsome speed back or you want to givethe user an option to dial downthe graphics quality because maybe they're on a phonethat is lower quality than you anticipated, you can give them an option to set this value, and that would let them play even if they havekind of a crappy phone.

So the goal istarget all devices, right? That's a technical problem.

You can deal with it.

I did.

I kind of got lucky, but you follow the assumptionsthat we talked about today and the rulesthat we talked about today, and, you know, I think any game that's onthe Android Market today can be aggressivelycompatible.

Much harder is to understandyour user base.

Building a gamethat is compatible acrossa large number of people is actuallya very difficult problem.

And it's not justabout controls, and it's not justabout game content.

So I urge you when buildingAndroid games to think not just about thetechnical side of compatibility but also who your users are.

You know, who is this market? And what do theywant to play? And give them optionsto tune the game the way that they like.

And I think if you do that, you have a lot more– bigger chance of success.

This is the endof my remarks.

I sped through ita little bit 'cause we're runninga little late, but we do have timefor some questions.

If you're interestedin what I'm up to next, my company is calledRobot Invader.

We're at robotinvader.

com.

At Twitter, I'm at c_pruett.

And we don't haveanything to show quite yet.

We're only a month old.

But we will havesome pretty cool stuff.

So please stay tuned.

If you'd like to ask questions, please come upto one of these microphones.

Thank you very much.

[applause] man: You mentionedputting restrictions in the manifest to limitthe number of devices and there wasthe required true-false.

It's obvious what happenswhen it's true.

Well, how does the Marketrespond to that requirement when it's requiredto be false? Are you just saying, I would like this but I can deal with it? What's the point of just– what's the pointof even saying that? Pruett: Right.

So, the question is about what doesthe true-false thing do? Now, I believe you saidthat the false market will not treat itas a strict filter so it will allow devices–it'll basically ignore it.

But the purpose of the manifestis not just Android Market.

Right? The manifest is supposedto be all metadata about your application.

And Android applications aredesigned to be self-describing.

So you should be able to lookat the binary, understand what kind of devicesyou can install this on.

If you set that requirementto true, I don't think you'll even be able to installlike over ADB.

.

.

man: Right.

Pruett: a device–or application that doesn't meetthe requirements of that phone.

So I think that if you havethe field set to false, you're just giving Android moredata about your application, whether or notit acts upon it or now, or maybe in the future, I don't know.

I don't think that Marketin particular does anything with itif you set it to false.

But it's probably a good ideato give your.

.

.

give Android or Marketor any of those systems as much information aboutwhat you want as possible.

man: Okay.

Pruett: Over here.

man: So when you parsethe extension string– Pruett: Yes.

man: If you come across devices that don't supportthe extensions that you need, how do you fail gracefully? Do you just tell themyou can't do it? Or do you have a backup?Pruett: Sure.

Depends on the extension.

But, generally, extensions are fast paths for stuffthat's in the spec already.

So, for example, draw texturejust takes a texture and blitz it, you know, access-oriented to the screen at the scalethat you specify.

Now, if draw textureis not available, you could fall back on a quad that's orthographicallyprojected.

It's the same thing.

man: Is that what you do, or.

.

.

Pruett: That's what I do.

man: Yeah.

Pruett: Yes.

Or another exampleis vertex buffer objects.

Fast path for regularvertex buffers, right? Now, there are some things like you will seethe texture compression formats that are supportedin the GL extension string.

And if your texturesare all in some format that that device doesn't support, I don't know what you can do.

You can try toranscode them, but I think you'repretty much up a creek.

So, that's probablywhere you want to start putting requirementsin the manifest.

man:Right.

Thanks.

man: Before I ask my questionto play off his a little bit, then that's assuming you'renot filtering in your manifest.

Pruett: That's right.

man: Therefore.

.

.

not filtering in the Market.

Pruett: That's right.

man: And then you would wantto handle it at runtime.

Pruett: That's right.

So when I– one developerthat I spoke with was interested in transcodingbetween ATITC and DXT because those formats areactually at a binary level pretty similar.

So there might be liketricky things you could do.

Like, oh, I encodedall my stuff in DXT but I'm finding out, I'm actually runningon an ATITC device.

At runtime, I can transcodethis or something.

But probably what mostdevelopers are gonna do is not anythingthat complicated.

They're either just gonnause a non-proprietary format like ATC1, or they're gonna requiretheir manifest.

man: Okay.

So, my question.

Say I'm really motivatedand a little bit crazy and I want to do a gameor a graphical app that's really, you know, it's taking advantageof OpenGL ES2.

0.

It's targeted at, say, tablets.

It's really high end.

Pruett: Hmm-mm.

man: But I also want to appealto the lower-end devices.

Would I, I guess, in more general terms, would I develop two separateapps with different manifests just so that it could appearto any given user to be the same appbut only be exposed to the devicesthat are necessary? Pruett: You could.

You shouldn't.

Yeah, I mean, you certainlyhave the ability to do that.

The problemwith that approach is that then you'll havetwo apps on Market.

They will have two differentsets of ratings information.

They'll have to haveslightly different names.

And if one of your appsgets voted way up, it won't change the ratingof the other application.

man: What's the right wayto do that? Pruett:The right way to do that is preferably to have a singlebinary that you can do both in.

I mean, what would you changebetween the low-end version and the high-end version? Well, you'd probably changethe resolution of the textures.

You can do thatat runtime.

If you were gonna switchbetween, say, a fixed function rendererand a shader renderer, that actually might bequite a lot of work.

But it's also you know, a classyou can swap out at runtime.

The trick is.

.

.

to understandthat you can control, you know, which devices get yourapplication based on the spec.

But if at all possible, your ideal scenario is a single binarythat supports as wide a range of devicesas possible.

man: So then the concept ofa minimum spec in the manifest gets real tricky becauseit may not so much be minimum as I can do thiswith a low-end device but otherwise I can do thiswith a really high-end device.

How do you filter? Pruett: So in that case, you don't need to filter, right? If you can actually spanwhatever you're doing, if you can run on the low-enddevice and the high-end device, you don't need to tellthe manifest anything.

You're available to everybody.

Right? That's a runtimecomputation.

So what the manifest is foris for areas where you couldn't makeany concessions at runtime.

You can't just degradethe graphics quality or you know, resize the textures.

Like, if you havea shader based rendering engine and you don't careabout fixed function, you know, rendering engines, you can require OpenGL ES2.

0, and then those devicesjust won't get it.

Does that make sense?man: I think so.

Thank you.

Pruett: Thank you.

man: A little bit moreof a gut question.

Pruett:Sure.

man: How do you balancethose factors when you want to makethose changes like you did for the difficulty settingsand really you know, deciding, okay, well, this marketisn't big enough.

I want to includelower-end devices.

What do you use?Hard data? You know, do you get feedback? Pruett:Yeah.

man: You know, are you lookingat the competition? You know, those are the tough choices where you decide, okay, now I'm gonna spendtwo extra weeks working on this.

Pruett:That's a good question.

In my case, the game was writtenagainst the low end.

So all I had to dowas scale upwards, and that wasn't very hard.

In fact, it pretty muchworked out without me doing anything.

If you have shipped something, and now you go back and decide, well, you know, I shipped something and I required OpenGL ES2.

0, but I don't really need that and boy, there'sa lot of users out there who have older phonesI'd like to support, That's a little bit trickier.

Google does publish informationat developer.

android.

com.

about the distributionof screen sizes and also of Androidinstalled versions.

So you can get a sensefrom that data, you know, what the whole landscapelooks like.

You can also use.

.

.

there's a bunch of metrics that just got addeda couple of months ago to Android Market to seewho your users really are.

So if you got complaintsthat like, oh, it crasheson wide VGA displays and you went and you lookedand saw that 30% of users have wide VGA displays, that's probably an importantbug that you want to fix.

Above and beyond that, I highly recommend that you doyour own analytics.

You know, actually Android can– there's an Android versionof Google Analytics which you can plug in.

It's aboutthree lines of code.

Super simple.

You can send whatever datayou like back and it'll aggregate youon a webpage and give you graphsand all kinds of information.

And it'll tell youin excruciating detail, depending on what valuesyou send back, who your users are andwhat they're doing, you know.

I worked with a developerwho tracks how long it takes for users to completeeach level.

And they found outthat everybody completes the first level, and 80% of userscomplete the second level and 10% of userscomplete the third level.

Okay.

There's a problemwith the third level, right? Doing your own analytic trackingis super useful anyway.

That said, if you are talkingabout a group of users that you're notshipping to now and you don't knowhow large they are, it's pretty hard to tell, because absent data which only aggressivelycompatible applications have, you kind of have to either takea chance and ship something and try to make itas compatible as possible and ship itand see what happens.

or try to relyon third party data.

You know, in addition to Google, there's a flurryand there's, you know, AdMob and other peoplewho will publish data as well help youmake those decisions.

My suggestion would be to, you know, choose a minimum specas low down as possible, you know, try to makeyour minimum spec as old a phoneas you possibly can and then work from there.

man: Hey, Chris.

Pruett: What's going on? man:Everything's been pretty good for us native developersfor ARM so far, but Intel announcedmaybe a month ago that they're gonna startdoing X86-Android.

Pruett:Sure.

man: So what do you recommendwe do for compatibility for you know, ARM? Because right now, as it stands, my native games are I'm gonna have to dosomething quickly.

Pruett:Sure.

Sure.

Well, you know, the NDK will have to support ARM.

or I'm sorry– other architectures like X86– before you canreally do anything.

If you look at the NDK, there's actually alreadysome preliminary support for X86 chain in there.

And what's gonna happenwhen that support shows up or support for any otherarchitecture, say, you know, like a power PC device comes outtomorrow or something, right? The way it's gonna workis your.

.

.

if you look insideyour application that you built withthe NDK, you end up with a foldercalled libraries and in that folder, you have the output of your compilation, your libraries.

And actually, you can havemultiples versions for different architectures.

So, right now, you can choose to compile against both ARM 5and ARM 7.

And it will automaticallyload the correct one.

In the future, what I would expect is that you hit the sameindicate build command and if you setyour make file up correctly, you will get a libraryfor X86 as well as for ARM.

Now, that makes a lot ofassumptions, right? It assumes that your codeis first of all just gonna compile againstan X86 code base and that your, you know, data is a little [indistinct]or whatever.

But I think that functionallythe way it'll work is that your application– it's already a sort ofa fat binary, right? It's already able tosupport multiple architectures, and they will just addarchitectures onto that.

man:But do you think the Market– that makes perfect sense.

But do you thinkthe Market is gonna have an opt inor an opt out? Because the one thing I don'twant to have happen is, you know, all the newX86 devices roll out, and just we're crashingon them.

Pruett: Right.

That's actually already there.

If you.

.

.

one thing I didn'tmention in this talk is that Market willlook at your manifest to figure out its filters, but it also draws filters from intrinsic informationabout your application.

So for example, if you targetARM v7 right now, you will not appearon ARM v5 devices 'cause you said I can't supportARM v5 by nature of you compiling only againstARM v7.

So, if you havea native application and some other architectureshifts, by default, you're not gonna show upon that device until you explicitlygo back and recompile and then ship an update.

Did that answeryour question? man:Oh, yeah.

Pruett: Okay.

Are thereany other questions? If not, thank you very muchfor coming.

Appreciate it.

http://dominickulnq334.simplesite.com/445529094
https://telegra.ph/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick-03-29
https://remingtonnapf189.tumblr.com/post/613899071435980800/the-best-apk-ever-had-for-movies-tv-shows-live
http://jaidenggub586.bravesites.com/entries/general/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
https://www.smore.com/fryn8-the-best-apk-ever-had-for-movies
https://johnnyoodd714.edublogs.org/2020/03/29/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick/
http://remingtonevov035.xtgem.com/the%20best%20apk%20ever%20had%20for%20movies%20tv%20shows%20live%20sports%20on%20amazon%20firestick
http://codygord487.wpsuo.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
https://penzu.com/p/61092dff
https://beckettuyqj.bloggersdelight.dk/2020/03/29/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick/
https://5e807daa432a5.site123.me/#section-5e807e73ee725
https://www.storeboard.com/blogs/general/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick/2232254
https://alexisqmrw320.skyrock.com/3331337730-THE-BEST-APK-EVER-HAD-FOR-MOVIES-&-TV-SHOWS-LIVE-SPORTS-ON-AMAZON.html
http://angelomynd693.iamarrows.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://chanceuxnh883.jigsy.com/entries/general/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://rowankowh478.yousher.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
https://b3.zcubes.com/v.aspx?mid=3465265&title=the-best-apk-ever-had-for-movies--tv-shows-live-sports-on-amazon-firestick
http://chancefgnm659.huicopper.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://p7yienv232.nation2.com/the-best-apk-ever-had-for-movies-tv-shows-live-s
http://my-smart-blog-6331.223386.n8.nabble.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick-tp2.html
http://augustpkiz138.nikehyperchasesp.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://edwinntgj628.unblog.fr/2020/03/29/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick/
http://trentontukm195.institutoalvorada.org/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://hectoryiit207.fotosdefrases.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://rylangzig633.zoninrewards.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://elliottuvhv573.image-perth.org/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://raymonddbem143.lowescouponn.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://jaredmosc583.lucialpiazzale.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://elliotuuio177.bearsfanteamshop.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://landenhsfe732.cavandoragh.org/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://ricardoggsw165.raidersfanteamshop.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://kylerrlcz760.tearosediner.net/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://paxtongnet120.theglensecret.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://alexisfmgi686.timeforchangecounselling.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://rowanvlwm429.trexgame.net/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://rafaelwlsb100.westbluestudio.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://lukaswmpd781.almoheet-travel.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
http://mylestknf173.theburnward.com/the-best-apk-ever-had-for-movies-tv-shows-live-sports-on-amazon-firestick
https://t0ncrsf810.wixsite.com/beckettiziq287/post/best-kodi-addon-2018-for-movies-tv-shows
https://milohuwc389.hatenablog.com/entry/2020/03/29/145647
https://www.liveinternet.ru/users/v0ebour309/post468592353//
https://sergioosfe653.wordpress.com/2020/03/29/best-kodi-addon-2018-for-movies-tv-shows/
https://www.evernote.com/shard/s679/sh/f577f86e-33ea-4371-87df-8ea8dd2738d1/3b06dc7abe82d9991275bec2548353e9
https://diigo.com/0h5aoa
http://damiensngg460.simplesite.com/445529242
https://telegra.ph/best-kodi-addon-2018-for-movies-tv-shows-03-29
https://johnnywfwt105.tumblr.com/post/613901517369589760/best-kodi-addon-2018-for-movies-tv-shows
http://troyaffs813.bravesites.com/entries/general/best-kodi-addon-2018-for-movies-tv-shows
https://www.smore.com/8fnpq-best-kodi-addon-2018-for-movies-tv
https://rowanllqq429.edublogs.org/2020/03/29/best-kodi-addon-2018-for-movies-tv-shows/
http://riverqejk053.xtgem.com/best%20kodi%20addon%202018%20for%20movies%20tv%20shows
http://remingtonhbxy454.wpsuo.com/best-kodi-addon-2018-for-movies-tv-shows
https://penzu.com/p/f769efc7

Nhận xét

Bài đăng phổ biến từ blog này