├── .gitignore ├── .travis.yml ├── README.md ├── downloadSDKInfo.js ├── package.json ├── update.sh └── web ├── bower.json ├── controller.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | out/* 3 | node_modules/* 4 | */bower_components/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | script: 5 | - "npm run generate" 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android SDK offline download links generator 2 | -------------------------------------------- 3 | This tool help to you generate latest offline down links for Android SDK. This is helpful when you don't have internet access or speed is low. 4 | 5 | You can use your favorite download tool to download them. Then put these downloaded files into sdk/temp folder and open Android SDK Manager. Click the items in Android SDK Manager, it will read from your downloaded cache. 6 | 7 | If your can't get the Android SDK Manager items or if they are not update, or the download speed is slow, please try to use the [proxy listed here](http://www.androiddevtools.cn/) 8 | 9 | If it helps, please star and spread this project :) 10 | 11 | # Online site 12 | http://www.april1985.com/android-sdk-offline/ 13 | 14 | # Clone and run locally 15 | 16 | ``` 17 | git clone https://github.com/derekhe/android-sdk-offline.git 18 | npm install 19 | cd web 20 | bower install 21 | cd .. 22 | npm install -g http-server 23 | //You may need to set proxy if your network is blocked 24 | npm run generate 25 | npm run serve 26 | ``` 27 | 28 | open your browser and navigate to localhost:8080 29 | 30 | Hope this tool can help you 31 | ====================== 32 | This tool is automatically updated everyday on my digitalocean server. I suggest you to serve your server on digitalocean. I can give a 5 starts for my 3+ years of experience of using digitalocean. Please help me reigster a digitalocean account to support this tool keeping update. [Please Register Here](https://m.do.co/c/4bc532e3ef94) 33 | -------------------------------------------------------------------------------- /downloadSDKInfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Created by hesicong on 2015/7/18. 4 | */ 5 | 6 | var request = require('request-promise'); 7 | var async = require('async'); 8 | var _ = require('lodash'); 9 | var xml2js = require('xml2js'); 10 | var path = require('path'); 11 | var fs = require('fs'); 12 | 13 | var repo = [ 14 | 'http://dl.google.com/android/repository/repository-11.xml', 15 | 'http://dl.google.com/android/repository/repository-10.xml', 16 | 'http://dl.google.com/android/repository/addon-6.xml', 17 | 'http://dl.google.com/android/repository/addon.xml', 18 | 'http://dl.google.com/android/repository/extras/intel/addon.xml', 19 | 'http://dl.google.com/android/repository/sys-img/android-tv/sys-img.xml', 20 | 'http://dl.google.com/android/repository/sys-img/android-wear/sys-img.xml', 21 | 'http://dl.google.com/android/repository/sys-img/android/sys-img.xml', 22 | 'http://dl.google.com/android/repository/sys-img/google_apis/sys-img.xml', 23 | 'http://dl.google.com/android/repository/sys-img/x86/addon-x86.xml', 24 | 'https://dl-ssl.google.com/glass/gdk/addon.xml' 25 | ]; 26 | 27 | function fixUrl(dirname, js) { 28 | if (!_.isObject(js)) return; 29 | 30 | _.each(_.keys(js), function (k) { 31 | if (k == "url") { 32 | if (!_.startsWith(js[k], "http")) { 33 | js[k] = dirname + "/" + js[k]; 34 | } 35 | } 36 | 37 | fixUrl(dirname, js[k]); 38 | }); 39 | } 40 | 41 | function fetchDetailInfomation(callback) { 42 | async.map(repo, function (url, done) { 43 | console.log("Fetching from", url); 44 | request(url).then(function (body) { 45 | xml2js.parseString(body, { 46 | tagNameProcessors: [function (tag) { 47 | return tag.replace("sdk:", ""); 48 | }] 49 | }, function (err, js) { 50 | var dirname = path.dirname(url); 51 | fixUrl(dirname, js); 52 | done(err, js); 53 | }); 54 | }).catch(function (err) { 55 | console.error("can't fetch " + url + " skip", err); 56 | done(null, null); 57 | }) 58 | } 59 | , function (err, results) { 60 | var mergeAllResults = {}; 61 | _.each(results, function (r) { 62 | mergeAllResults = _.merge(mergeAllResults, r); 63 | }); 64 | 65 | var mergedAllCategory = {}; 66 | _.each(_.keys(mergeAllResults), function (k) { 67 | mergedAllCategory = _.merge(mergeAllResults[k], mergedAllCategory); 68 | }); 69 | 70 | console.log("Got all category"); 71 | callback(null, mergedAllCategory); 72 | }) 73 | } 74 | 75 | function getVersion(rev) { 76 | var major = rev["major"] || ''; 77 | var minor = rev["minor"] || ''; 78 | var micro = rev["micro"] || ''; 79 | return major + "." + minor + "." + micro; 80 | } 81 | 82 | function getApis(sdk) { 83 | var apiLevels = {}; 84 | addApi(sdk, "add-on", "Addon", apiLevels); 85 | addApi(sdk, "platform", "SDK Platform", apiLevels); 86 | addApi(sdk, "sample", "Samples for SDK", apiLevels); 87 | addApi(sdk, "system-image", "System Image", apiLevels); 88 | addApi(sdk, "source", "Sources for Android SDK", apiLevels); 89 | 90 | apiLevels = _.map(apiLevels, function (v) { 91 | return v; 92 | }); 93 | return apiLevels; 94 | } 95 | 96 | function isObsolete(item) { 97 | return !_.isUndefined(item["obsolete"]); 98 | } 99 | 100 | function addApi(sdk, items, desc, apiLevels) { 101 | _.each(sdk[items], function (item) { 102 | var codename = ''; 103 | if(!_.isUndefined(item["codename"])){ 104 | codename = item["codename"][0]; 105 | } 106 | 107 | var level = item["api-level"][0] + codename; 108 | var existLevel = apiLevels[level]; 109 | existLevel = existLevel || { 110 | apiLevel: level, 111 | items: [] 112 | }; 113 | var value = { 114 | description: function () { 115 | if (_.isUndefined(item['description'])) { 116 | return desc; 117 | } 118 | 119 | var description = desc + " " + item['description'][0]; 120 | 121 | if(!_.isUndefined(item['abi'])){ 122 | description = item['abi'][0] + " " + description; 123 | } 124 | 125 | return description; 126 | }(), 127 | version: item['revision'][0], 128 | archives: item['archives'], 129 | obsolete: isObsolete(item) 130 | }; 131 | existLevel.items.push(value); 132 | 133 | apiLevels[level] = existLevel; 134 | }); 135 | 136 | return apiLevels; 137 | } 138 | 139 | //DEBUG 140 | function writeToFile(sdk, callback) { 141 | fs.writeFileSync("temp.json", JSON.stringify(sdk, null, 2)); 142 | callback(null, sdk); 143 | } 144 | 145 | function getExtras(sdk) { 146 | return _.map(sdk['extra'], function (item) { 147 | return { 148 | description: item['name-display'], 149 | version: getVersion(item['revision'][0]), 150 | archives: item['archives'], 151 | obsolete: isObsolete(item) 152 | } 153 | }); 154 | } 155 | 156 | function getTools(sdk) { 157 | var tools = []; 158 | tools = tools.concat(getTool(sdk, 'build-tool', 'Android SDK Build-tools')); 159 | tools = tools.concat(getTool(sdk, 'platform-tool', 'Android SDK Platform-tools')); 160 | tools = tools.concat(getTool(sdk, 'tool', 'Android SDK Tools')); 161 | return tools; 162 | } 163 | 164 | function getTool(sdk, s, desc) { 165 | return _.map(sdk[s], function (tag) { 166 | return { 167 | description: desc, 168 | version: getVersion(tag["revision"][0]), 169 | obsolete: isObsolete(tag), 170 | archives: tag["archives"] 171 | } 172 | }) 173 | } 174 | 175 | function getDate() { 176 | var d = new Date(); 177 | return d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate(); 178 | } 179 | 180 | function postProcess(sdk, callback) { 181 | console.log("Post processing"); 182 | var info = { 183 | tools: getTools(sdk), 184 | apis: getApis(sdk), 185 | extras: getExtras(sdk), 186 | updated: getDate() 187 | }; 188 | 189 | var outputFileName = "web/sdk.json"; 190 | console.log("Writing to", outputFileName); 191 | fs.writeFileSync(outputFileName, JSON.stringify(info, null, 2)); 192 | callback(null, sdk); 193 | } 194 | 195 | async.waterfall([ 196 | fetchDetailInfomation, 197 | writeToFile, 198 | postProcess 199 | ], function (err, results) { 200 | console.log("Done"); 201 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "android-sdk-offline", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "hesicong", 6 | "license": "ISC", 7 | "description": "", 8 | "scripts": { 9 | "generate": "node downloadSDKInfo.js", 10 | "serve": "http-server ./web/" 11 | }, 12 | "dependencies": { 13 | "async": "^1.3.0", 14 | "lodash": "^3.10.0", 15 | "request-promise": "^0.4.2", 16 | "xml2js": "^0.4.9" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | node downloadSDKInfo.js 2 | cp web/sdk.json ../android-sdk-offline-pages/ 3 | cd ../android-sdk-offline-pages/ 4 | git commit -am "Updated" 5 | git push 6 | 7 | -------------------------------------------------------------------------------- /web/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "ignore": [ 6 | "**/.*", 7 | "node_modules", 8 | "bower_components", 9 | "test", 10 | "tests" 11 | ], 12 | "dependencies": { 13 | "angular": "~1.4.3", 14 | "angular-resource": "~1.4.3", 15 | "ng-lodash": "~0.2.3", 16 | "semantic-ui": "~2.0.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /web/controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hesicong on 2015/7/18. 3 | */ 4 | 5 | angular.module("app", ['ngResource', 'ngLodash']) 6 | .controller("controller", ['$scope', '$resource', 'lodash', function (vm, resource, _) { 7 | vm.showObsolete = false; 8 | vm.showObsoleteFn = function (item) { 9 | if (vm.showObsolete) { 10 | return true; 11 | } 12 | 13 | return item.obsolete == vm.showObsolete; 14 | }; 15 | 16 | vm.getOS = function (archive) { 17 | if (_.isUndefined(archive['host-os'])) return "any"; 18 | return archive['host-os'][0]; 19 | }; 20 | 21 | vm.os = { 22 | windows: true, 23 | macosx: true, 24 | linux: true, 25 | any: true 26 | }; 27 | 28 | vm.getDownloadLinks = function () { 29 | var links = ''; 30 | 31 | _.each(downloadLinks, function (link) { 32 | links = links + link + "\r\n"; 33 | }); 34 | 35 | return links 36 | }; 37 | 38 | var downloadLinks = []; 39 | 40 | vm.changeDownloadLink = function (item) { 41 | if (item.download) { 42 | _.each(item.archives[0].archive, function (d) { 43 | if (vm.os[vm.getOS(d)]) { 44 | downloadLinks.push(d.url); 45 | } 46 | }); 47 | } 48 | else { 49 | _.each(item.archives[0].archive, function (d) { 50 | console.log(d.url, downloadLinks); 51 | _.remove(downloadLinks, function (n) { 52 | return n === d.url; 53 | }); 54 | }); 55 | } 56 | 57 | downloadLinks = _.uniq(downloadLinks); 58 | }; 59 | 60 | vm.selectLevel = function (level) { 61 | _.each(level.items, function (item) { 62 | item.download = level.download; 63 | 64 | vm.changeDownloadLink(item); 65 | }); 66 | }; 67 | 68 | resource("sdk.json").get(function (sdk) { 69 | vm.sdk = sdk; 70 | }); 71 | }]) 72 | .filter('bytes', function () { 73 | return function (bytes, precision) { 74 | if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return '-'; 75 | if (typeof precision === 'undefined') precision = 1; 76 | var units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], 77 | number = Math.floor(Math.log(bytes) / Math.log(1024)); 78 | return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]; 79 | } 80 | }); -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
82 |
83 |
84 |
87 |
88 | |
89 | 90 | {{::item.version}} 91 | | 92 |93 | 100 | | 101 |
120 |
121 |
122 |
125 |
126 | |
127 | 128 | {{item.version}} 129 | | 130 |131 | 138 | | 139 |
154 |
155 |
156 |
159 |
160 | |
161 | 162 | {{::item.version}} 163 | | 164 |165 | 172 | | 173 |