├── .gitignore
├── LICENSE
├── README.md
├── android
├── AndroidManifest.xml
├── android.iml
├── build.xml
├── proguard.cfg
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── main.xml
│ ├── values-v11
│ │ └── styles.xml
│ └── values
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── google
│ └── android
│ └── apps
│ └── proofer
│ ├── DesktopViewerActivity.java
│ └── SystemUiHider.java
├── art
├── icon.icns
├── icon.psd
├── icon_128.png
├── icon_16.png
├── icon_16.psd
├── icon_256.png
├── icon_32.png
├── icon_32.psd
└── icon_512.png
├── build.xml
├── desktop
├── assets
│ ├── icon_128.png
│ ├── icon_16.png
│ ├── icon_32.png
│ ├── icon_512.png
│ ├── linux
│ │ └── adb
│ ├── mac
│ │ └── adb
│ └── windows
│ │ ├── AdbWinApi.dll
│ │ ├── AdbWinUsbApi.dll
│ │ └── adb.exe
├── build.xml
├── desktop.iml
├── lib
│ └── AppleJavaExtensions.jar
└── src
│ ├── META-INF
│ └── MANIFEST.MF
│ └── com
│ └── google
│ └── android
│ └── desktop
│ └── proofer
│ ├── AdbRunner.java
│ ├── Config.java
│ ├── ControllerForm.form
│ ├── ControllerForm.java
│ ├── Proofer.java
│ ├── ProoferException.java
│ ├── RegionSelector.java
│ ├── Util.java
│ └── os
│ ├── DefaultBinder.java
│ ├── MacBinder.java
│ └── OSBinder.java
├── mac
├── Info.plist
├── PkgInfo
├── package_mac.sh
└── package_mac_old_dmg.sh
└── proofer.ipr
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | Thumbs.db
3 | *.pyc
4 | *.iws
5 | _sandbox
6 | local.properties
7 | gen
8 | bin
9 | out
10 | desktop/assets/Proofer.apk
11 | android/proofer.keystore
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://unmaintained.tech/)
2 |
3 | AndroidDesignPreview
4 | ====================
5 |
6 | Android Design Preview is a tool that lets you mirror a portion of your desktop to your device. Useful for visual designers as well as developers, this tool can help streamline your high-fidelity mockup workflow.
7 |
8 | **[Download Android Design Preview](https://github.com/romannurik/AndroidDesignPreview/releases)**
9 |
--------------------------------------------------------------------------------
/android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/android/android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/android/build.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
44 |
45 |
46 |
55 |
56 |
57 |
58 |
62 |
63 |
64 |
66 |
78 |
79 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/android/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembers class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembers class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers class * extends android.app.Activity {
30 | public void *(android.view.View);
31 | }
32 |
33 | -keepclassmembers enum * {
34 | public static **[] values();
35 | public static ** valueOf(java.lang.String);
36 | }
37 |
38 | -keep class * implements android.os.Parcelable {
39 | public static final android.os.Parcelable$Creator *;
40 | }
41 |
--------------------------------------------------------------------------------
/android/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-16
12 | key.store=proofer.keystore
13 | key.alias=proofer
14 |
--------------------------------------------------------------------------------
/android/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/android/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/android/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/android/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
21 |
22 |
25 |
26 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/android/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/android/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | Android Design Preview
19 | Design Preview
20 | Not connected.
21 | Disconnected
22 | Reconnected
23 |
24 |
--------------------------------------------------------------------------------
/android/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/android/src/com/google/android/apps/proofer/DesktopViewerActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.apps.proofer;
18 |
19 | import android.app.Activity;
20 | import android.graphics.Bitmap;
21 | import android.graphics.BitmapFactory;
22 | import android.graphics.drawable.BitmapDrawable;
23 | import android.os.Build;
24 | import android.os.Bundle;
25 | import android.os.Handler;
26 | import android.os.Message;
27 | import android.util.Log;
28 | import android.view.MotionEvent;
29 | import android.view.View;
30 | import android.view.View.OnTouchListener;
31 | import android.view.ViewTreeObserver;
32 | import android.view.WindowManager;
33 | import android.widget.TextView;
34 | import android.widget.Toast;
35 |
36 | import java.io.BufferedInputStream;
37 | import java.io.DataOutputStream;
38 | import java.io.IOException;
39 | import java.net.ServerSocket;
40 | import java.net.Socket;
41 |
42 | public class DesktopViewerActivity extends Activity implements
43 | ViewTreeObserver.OnGlobalLayoutListener {
44 | private static final String TAG = "DesktopViewerActivity";
45 | private static final int PORT_DEVICE = 7800;
46 |
47 | private View mTargetView;
48 | private TextView mStatusTextView;
49 |
50 | private boolean mKillServer;
51 |
52 | private boolean mWasAtSomePointConnected = false;
53 | private boolean mConnected = false;
54 |
55 | private int mOffsetX;
56 | private int mOffsetY;
57 |
58 | private int mWidth;
59 | private int mHeight;
60 |
61 | private final Object mDataSyncObject = new Object();
62 | private byte[] mImageData;
63 |
64 | private SystemUiHider mSystemUiHider;
65 |
66 | @Override
67 | public void onCreate(Bundle savedInstanceState) {
68 | super.onCreate(savedInstanceState);
69 |
70 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
71 | // Pre-Honeycomb this is the best we can do.
72 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
73 | }
74 |
75 | setContentView(R.layout.main);
76 |
77 | mStatusTextView = (TextView) findViewById(R.id.status_text);
78 |
79 | mTargetView = findViewById(R.id.target);
80 | mTargetView.setOnTouchListener(mTouchListener);
81 | mTargetView.getViewTreeObserver().addOnGlobalLayoutListener(this);
82 |
83 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
84 | mSystemUiHider = new SystemUiHider(mTargetView);
85 | mSystemUiHider.setup(getWindow());
86 | } else {
87 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
88 | }
89 | }
90 |
91 | @Override
92 | public void onResume() {
93 | super.onResume();
94 | mKillServer = false;
95 | new Thread(mSocketThreadRunnable).start();
96 | }
97 |
98 | public void onPause() {
99 | super.onPause();
100 | mKillServer = true;
101 | }
102 |
103 | private OnTouchListener mTouchListener = new OnTouchListener() {
104 | float mDownX;
105 | float mDownY;
106 | float mDownOffsetX;
107 | float mDownOffsetY;
108 |
109 | @Override
110 | public boolean onTouch(View v, MotionEvent event) {
111 | if (mSystemUiHider != null) {
112 | mSystemUiHider.delay();
113 | }
114 |
115 | int action = event.getAction();
116 | switch (action) {
117 | case MotionEvent.ACTION_DOWN:
118 | mDownX = event.getX();
119 | mDownY = event.getY();
120 | mDownOffsetX = mOffsetX;
121 | mDownOffsetY = mOffsetY;
122 | break;
123 |
124 | case MotionEvent.ACTION_MOVE:
125 | mOffsetX = (int) (mDownOffsetX + (mDownX - event.getX()));
126 | mOffsetY = (int) (mDownOffsetY + (mDownY - event.getY()));
127 | if (mOffsetX < 0) {
128 | mOffsetX = 0;
129 | }
130 | if (mOffsetY < 0) {
131 | mOffsetY = 0;
132 | }
133 | break;
134 | }
135 | return true;
136 | }
137 | };
138 |
139 | private Handler mHandler = new Handler() {
140 | public void handleMessage(Message msg) {
141 | Bitmap bm = (Bitmap) msg.obj;
142 |
143 | if (bm == null) {
144 | // Not connected
145 | if (mConnected) {
146 | // Disconnected (was previously connected)
147 | Toast.makeText(DesktopViewerActivity.this,
148 | R.string.toast_disconnected, Toast.LENGTH_SHORT).show();
149 | }
150 | } else {
151 | // Connected
152 | //noinspection deprecation
153 | mTargetView.setBackgroundDrawable(new BitmapDrawable(getResources(), bm));
154 | mStatusTextView.setVisibility(View.GONE);
155 |
156 | if (!mConnected && mWasAtSomePointConnected) {
157 | // Reconnected (was at some point connected, then connection list, now it's
158 | // back)
159 | Toast.makeText(DesktopViewerActivity.this,
160 | R.string.toast_reconnected, Toast.LENGTH_SHORT).show();
161 | }
162 |
163 | mWasAtSomePointConnected = true;
164 | }
165 |
166 | mConnected = (bm != null);
167 | }
168 | };
169 |
170 | public void onGlobalLayout() {
171 | updateDimensions();
172 | }
173 |
174 | private void updateDimensions() {
175 | synchronized (mDataSyncObject) {
176 | mWidth = mTargetView.getWidth();
177 | mHeight = mTargetView.getHeight();
178 | mImageData = new byte[mWidth * mHeight * 3];
179 | }
180 | }
181 |
182 | private int readFully(BufferedInputStream bis, byte[] data, int offset, int len)
183 | throws IOException {
184 | int count = 0;
185 | int got = 0;
186 | while (count < len) {
187 | got = bis.read(data, count, len - count);
188 |
189 | if (got >= 0) {
190 | count += got;
191 | } else {
192 | break;
193 | }
194 | }
195 |
196 | if (Log.isLoggable(TAG, Log.DEBUG)) {
197 | Log.d(TAG, "Got " + got + " bytes");
198 | }
199 | return got;
200 | }
201 |
202 | private Runnable mSocketThreadRunnable = new Runnable() {
203 | public void run() {
204 | while (true) {
205 | ServerSocket server = null;
206 |
207 | try {
208 | Thread.sleep(1000);
209 | server = new ServerSocket(PORT_DEVICE);
210 | } catch (Exception e) {
211 | Log.e(TAG, "Error creating server socket", e);
212 | continue;
213 | }
214 |
215 | while (true) {
216 | try {
217 | Socket socket = server.accept();
218 | Log.i(TAG, "Got connection request");
219 | BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
220 | DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
221 |
222 | while (!mKillServer) {
223 | Thread.sleep(50);
224 | synchronized (mDataSyncObject) {
225 | dos.writeInt(mOffsetX);
226 | dos.writeInt(mOffsetY);
227 | dos.writeInt(mWidth);
228 | dos.writeInt(mHeight);
229 | dos.flush();
230 |
231 | if (Log.isLoggable(TAG, Log.DEBUG)) {
232 | Log.d(TAG, "Wrote request");
233 | }
234 |
235 | byte[] inlen = new byte[4];
236 | readFully(bis, inlen, 0, 4);
237 | int len = ((inlen[0] & 0xFF) << 24) | ((inlen[1] & 0xFF) << 16)
238 | | ((inlen[2] & 0xFF) << 8) | (inlen[3] & 0xFF);
239 | readFully(bis, mImageData, 0, len);
240 |
241 | Bitmap bm = BitmapFactory.decodeByteArray(mImageData, 0, len);
242 | mHandler.sendMessage(mHandler.obtainMessage(1, bm));
243 | }
244 | }
245 |
246 | bis.close();
247 | dos.close();
248 | socket.close();
249 | server.close();
250 | return;
251 | } catch (Exception e) {
252 | Log.e(TAG, "Exception transferring data", e);
253 | mHandler.sendMessage(mHandler.obtainMessage(1, null));
254 | }
255 | }
256 | }
257 | }
258 | };
259 | }
260 |
--------------------------------------------------------------------------------
/android/src/com/google/android/apps/proofer/SystemUiHider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.apps.proofer;
18 |
19 | import android.os.Build;
20 | import android.os.Handler;
21 | import android.view.View;
22 | import android.view.Window;
23 | import android.view.WindowManager;
24 |
25 | public class SystemUiHider {
26 | private static final int HIDE_DELAY_MILLIS = 2000;
27 |
28 | private Handler mHandler;
29 | private View mView;
30 |
31 | public SystemUiHider(View view) {
32 | mView = view;
33 | }
34 |
35 | public void setup(Window window) {
36 | hideSystemUi();
37 |
38 | // Pre-Jellybean just hide the status bar
39 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
40 | window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
41 | }
42 |
43 | mHandler = new Handler();
44 | mView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
45 | @Override
46 | public void onSystemUiVisibilityChange(int visibility) {
47 | if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
48 | delay();
49 | }
50 | }
51 | });
52 | }
53 |
54 | private void hideSystemUi() {
55 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
56 | // On Jellybean we can use new System UI flags to allow showing titlebar/systembar
57 | // only upon touching the screen, while still having the content laid out in
58 | // the entire screen.
59 | mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
60 | | View.SYSTEM_UI_FLAG_FULLSCREEN
61 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
62 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
63 | } else {
64 | mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
65 | }
66 | }
67 |
68 | private Runnable mHideRunnable = new Runnable() {
69 | public void run() {
70 | hideSystemUi();
71 | }
72 | };
73 |
74 | public void delay() {
75 | mHandler.removeCallbacks(mHideRunnable);
76 | mHandler.postDelayed(mHideRunnable, HIDE_DELAY_MILLIS);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/art/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon.icns
--------------------------------------------------------------------------------
/art/icon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon.psd
--------------------------------------------------------------------------------
/art/icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon_128.png
--------------------------------------------------------------------------------
/art/icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon_16.png
--------------------------------------------------------------------------------
/art/icon_16.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon_16.psd
--------------------------------------------------------------------------------
/art/icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon_256.png
--------------------------------------------------------------------------------
/art/icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon_32.png
--------------------------------------------------------------------------------
/art/icon_32.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon_32.psd
--------------------------------------------------------------------------------
/art/icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/art/icon_512.png
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
46 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/desktop/assets/icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/icon_128.png
--------------------------------------------------------------------------------
/desktop/assets/icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/icon_16.png
--------------------------------------------------------------------------------
/desktop/assets/icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/icon_32.png
--------------------------------------------------------------------------------
/desktop/assets/icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/icon_512.png
--------------------------------------------------------------------------------
/desktop/assets/linux/adb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/linux/adb
--------------------------------------------------------------------------------
/desktop/assets/mac/adb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/mac/adb
--------------------------------------------------------------------------------
/desktop/assets/windows/AdbWinApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/windows/AdbWinApi.dll
--------------------------------------------------------------------------------
/desktop/assets/windows/AdbWinUsbApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/windows/AdbWinUsbApi.dll
--------------------------------------------------------------------------------
/desktop/assets/windows/adb.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/assets/windows/adb.exe
--------------------------------------------------------------------------------
/desktop/build.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
48 |
49 |
50 |
51 |
52 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/desktop/desktop.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/desktop/lib/AppleJavaExtensions.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidDesignPreview/7542db48de7124c11f39aba3909b5978c5cd6a85/desktop/lib/AppleJavaExtensions.jar
--------------------------------------------------------------------------------
/desktop/src/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: com.google.android.desktop.proofer.ControllerForm
3 | Implementation-Title: Android Design Preview
4 | Implementation-Vendor: Google Inc.
5 | Implementation-Version: 0.3.2
6 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/AdbRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer;
18 |
19 | import java.io.BufferedReader;
20 | import java.io.File;
21 | import java.io.IOException;
22 | import java.io.InputStreamReader;
23 | import java.util.ArrayList;
24 | import java.util.Collections;
25 | import java.util.List;
26 |
27 | public class AdbRunner {
28 | private static final int ADB_CACHE_VERSION = 1;
29 |
30 | private boolean debug = Util.isDebug();
31 |
32 | private File adbPath;
33 | private boolean ready = false;
34 |
35 | public AdbRunner() {
36 | try {
37 | prepareAdb();
38 | } catch (ProoferException e) {
39 | e.printStackTrace();
40 | }
41 | }
42 |
43 | private void prepareAdb() throws ProoferException {
44 | if (debug) {
45 | System.out.println("Preparing ADB");
46 | }
47 |
48 | int currentCacheVersion = Util.getCacheVersion();
49 | boolean forceExtract = currentCacheVersion < ADB_CACHE_VERSION;
50 |
51 | if (debug && forceExtract) {
52 | System.out.println("Current adb cache is old (version " + currentCacheVersion + "). "
53 | + "Upgrading to cache version " + ADB_CACHE_VERSION);
54 | }
55 |
56 | Util.OS currentOS = Util.getCurrentOS();
57 | if (currentOS == Util.OS.Other) {
58 | throw new ProoferException("Unknown operating system, cannot run ADB.");
59 | }
60 |
61 | switch (currentOS) {
62 | case Mac:
63 | case Linux:
64 | adbPath = extractAssetToCacheDirectory(currentOS.id + "/adb", "adb", forceExtract);
65 | break;
66 |
67 | case Windows:
68 | adbPath = extractAssetToCacheDirectory("windows/adb.exe", "adb.exe", forceExtract);
69 | extractAssetToCacheDirectory("windows/AdbWinApi.dll", "AdbWinApi.dll",
70 | forceExtract);
71 | extractAssetToCacheDirectory("windows/AdbWinUsbApi.dll", "AdbWinUsbApi.dll",
72 | forceExtract);
73 | break;
74 | }
75 |
76 | if (!adbPath.setExecutable(true)) {
77 | throw new ProoferException("Error setting ADB binary as executable.");
78 | }
79 |
80 | Util.putCacheVersion(ADB_CACHE_VERSION);
81 |
82 | ready = true;
83 | }
84 |
85 | private File extractAssetToCacheDirectory(String assetPath, String filename, boolean force)
86 | throws ProoferException {
87 | File outFile = new File(Util.getCacheDirectory(), filename);
88 | if (force || !outFile.exists()) {
89 | if (!Util.extractResource("assets/" + assetPath, outFile)) {
90 | throw new ProoferException("Error extracting to " + outFile.toString());
91 | }
92 | }
93 | return outFile;
94 | }
95 |
96 | public String adb(String[] args) throws ProoferException {
97 | if (debug) {
98 | StringBuilder sb = new StringBuilder();
99 | sb.append("Calling ADB: adb");
100 |
101 | for (String arg : args) {
102 | sb.append(" ");
103 | sb.append(arg);
104 | }
105 |
106 | System.out.println(sb.toString());
107 | }
108 |
109 | List argList = new ArrayList();
110 | argList.add(0, adbPath.getAbsolutePath());
111 | //argList.add(1, "-e");
112 | Collections.addAll(argList, args);
113 |
114 | int returnCode;
115 | Runtime runtime = Runtime.getRuntime();
116 | Process pr;
117 | StringBuilder sb = new StringBuilder(0);
118 |
119 | try {
120 | pr = runtime.exec(argList.toArray(new String[argList.size()]));
121 |
122 | // TODO: do something with pr.getErrorStream if needed.
123 | BufferedReader reader = new BufferedReader(new InputStreamReader(pr.getInputStream()));
124 |
125 | String line = "";
126 | while ((line = reader.readLine()) != null) {
127 | sb.append(line);
128 | sb.append("\n");
129 | }
130 |
131 | returnCode = pr.waitFor();
132 |
133 | } catch (InterruptedException e) {
134 | throw new ProoferException(e);
135 | } catch (IOException e) {
136 | throw new ProoferException(e);
137 | }
138 |
139 | String out = sb.toString();
140 |
141 | if (debug) {
142 | System.out.println("Output:" + out);
143 | }
144 |
145 | if (returnCode != 0) {
146 | throw new ProoferException("ADB returned error code " + returnCode);
147 | }
148 |
149 | return out;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/Config.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer;
18 |
19 | public class Config {
20 | public static final String ANDROID_APP_PACKAGE_NAME = "com.google.android.apps.proofer";
21 | public static final int PORT_LOCAL = 6800;
22 | public static final int PORT_DEVICE = 7800;
23 | }
24 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/ControllerForm.form:
--------------------------------------------------------------------------------
1 |
2 |
107 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/ControllerForm.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer;
18 |
19 | import com.google.android.desktop.proofer.os.OSBinder;
20 |
21 | import java.awt.*;
22 | import java.awt.datatransfer.DataFlavor;
23 | import java.awt.datatransfer.Transferable;
24 | import java.awt.datatransfer.UnsupportedFlavorException;
25 | import java.awt.dnd.DnDConstants;
26 | import java.awt.dnd.DropTarget;
27 | import java.awt.dnd.DropTargetAdapter;
28 | import java.awt.dnd.DropTargetDropEvent;
29 | import java.awt.dnd.DropTargetListener;
30 | import java.awt.event.ActionEvent;
31 | import java.awt.event.ActionListener;
32 | import java.awt.event.WindowEvent;
33 | import java.awt.event.WindowListener;
34 | import java.awt.image.BufferedImage;
35 | import java.io.File;
36 | import java.io.FileInputStream;
37 | import java.io.FileNotFoundException;
38 | import java.io.FileOutputStream;
39 | import java.io.FilenameFilter;
40 | import java.io.IOException;
41 | import java.util.Arrays;
42 | import java.util.List;
43 | import java.util.Properties;
44 |
45 | import javax.imageio.ImageIO;
46 | import javax.swing.*;
47 |
48 | public class ControllerForm
49 | implements WindowListener, OSBinder.Callbacks, Proofer.ProoferCallbacks,
50 | RegionSelector.RegionChangeCallback {
51 | private boolean debug = Util.isDebug();
52 |
53 | private JFrame frame;
54 | private JPanel contentPanel;
55 | private JButton reinstallButton;
56 | private JLabel statusLabel;
57 | private JButton sourceButton;
58 | private JRadioButton localFileSourceButton;
59 | private JRadioButton screenCaptureSourceButton;
60 |
61 | private RegionSelector regionSelector;
62 | private Proofer proofer;
63 |
64 | public ControllerForm() {
65 | OSBinder.getBinder(this);
66 | setupUI();
67 | setupProofer();
68 | }
69 |
70 | public static void main(String[] args) {
71 | // OSX only
72 | // System.setProperty("com.apple.mrj.application.apple.menu.about.name",
73 | // "Android Design Preview");
74 | new ControllerForm();
75 | }
76 |
77 | private void setupProofer() {
78 | proofer = new Proofer(this);
79 |
80 | try {
81 | proofer.setupPortForwarding();
82 | } catch (ProoferException e) {
83 | e.printStackTrace();
84 | }
85 |
86 | try {
87 | proofer.installAndroidApp(false);
88 | proofer.runAndroidApp();
89 | } catch (ProoferException e) {
90 | e.printStackTrace();
91 | }
92 |
93 | proofer.startConnectionLoop();
94 | proofer.setRequestedSourceRegion(regionSelector.getRegion());
95 | }
96 |
97 | private void setupUI() {
98 | frame = new JFrame(ControllerForm.class.getName());
99 | frame.setTitle("Android Design Preview");
100 | frame.setIconImages(Arrays.asList(Util.getAppIconMipmap()));
101 | frame.setAlwaysOnTop(true);
102 | frame.setMinimumSize(new Dimension(250, 200));
103 |
104 | frame.setLocationByPlatform(true);
105 | tryLoadFrameConfig();
106 |
107 | frame.setContentPane(contentPanel);
108 | frame.setResizable(false);
109 | frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
110 | frame.setVisible(true);
111 | frame.addWindowListener(this);
112 |
113 | reinstallButton.addActionListener(new ActionListener() {
114 | public void actionPerformed(ActionEvent actionEvent) {
115 | try {
116 | proofer.installAndroidApp(true);
117 | proofer.runAndroidApp();
118 | } catch (ProoferException e) {
119 | JOptionPane.showMessageDialog(frame,
120 | "Couldn't install the app: " + e.getMessage()
121 | + "\n"
122 | + "\nPlease make sure your device is connected over USB and "
123 | + "\nthat USB debugging is enabled on your device under "
124 | + "\nSettings > Applications > Development or"
125 | + "\nSettings > Developer options.",
126 | "Android Design Preview",
127 | JOptionPane.ERROR_MESSAGE);
128 | e.printStackTrace();
129 | }
130 | }
131 | });
132 |
133 | regionSelector = new RegionSelector(this);
134 |
135 | ActionListener sourceTypeChangeListener = new ActionListener() {
136 | @Override
137 | public void actionPerformed(ActionEvent actionEvent) {
138 | switchSourceType(actionEvent.getActionCommand());
139 | }
140 | };
141 |
142 | localFileSourceButton.setActionCommand(Proofer.SOURCE_TYPE_FILE);
143 | localFileSourceButton.addActionListener(sourceTypeChangeListener);
144 | screenCaptureSourceButton.setActionCommand(Proofer.SOURCE_TYPE_SCREEN);
145 | screenCaptureSourceButton.addActionListener(sourceTypeChangeListener);
146 |
147 | sourceButton.addActionListener(new ActionListener() {
148 | public void actionPerformed(ActionEvent actionEvent) {
149 | if (Proofer.SOURCE_TYPE_FILE.equals(proofer.getSourceType())) {
150 | // use the native file dialog on the mac
151 | FileDialog dialog = new FileDialog(frame,
152 | "Select Mockup File", FileDialog.LOAD);
153 | dialog.setFilenameFilter(new FilenameFilter() {
154 | @Override
155 | public boolean accept(File file, String s) {
156 | s = s.toLowerCase();
157 | return s.endsWith(".png")
158 | || s.endsWith(".jpg")
159 | || s.endsWith(".gif")
160 | || s.endsWith(".jpeg");
161 | }
162 | });
163 | dialog.setAlwaysOnTop(true);
164 | dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
165 | dialog.setVisible(true);
166 | loadFile(new File(dialog.getDirectory(), dialog.getFile()));
167 |
168 | } else {
169 | regionSelector.showWindow(!regionSelector.isVisible());
170 | }
171 | }
172 | });
173 |
174 | new DropTarget(frame, fileDropListener);
175 | }
176 |
177 | private DropTargetListener fileDropListener = new DropTargetAdapter() {
178 | @Override
179 | public void drop(DropTargetDropEvent event) {
180 | // http://blog.christoffer.me/2011/01/drag-and-dropping-files-to-java-desktop.html
181 | event.acceptDrop(DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK);
182 | Transferable transferable = event.getTransferable();
183 | DataFlavor[] flavors = transferable.getTransferDataFlavors();
184 | for (DataFlavor flavor : flavors) {
185 | try {
186 | if (flavor.isFlavorJavaFileListType()) {
187 | List files = (List) transferable.getTransferData(flavor);
188 | if (files.size() > 0) {
189 | File file = files.get(0);
190 | localFileSourceButton.setSelected(true);
191 | switchSourceType(Proofer.SOURCE_TYPE_FILE);
192 | loadFile(file);
193 | event.dropComplete(true);
194 | }
195 | }
196 | } catch (IOException e) {
197 | if (debug) {
198 | e.printStackTrace();
199 | }
200 | } catch (UnsupportedFlavorException e) {
201 | if (debug) {
202 | e.printStackTrace();
203 | }
204 | }
205 | }
206 | event.rejectDrop();
207 | }
208 | };
209 |
210 | private void loadFile(File file) {
211 | BufferedImage bi;
212 | try {
213 | bi = ImageIO.read(file);
214 | } catch (IOException e) {
215 | JOptionPane.showMessageDialog(frame,
216 | "Error loading image.", "Android Design Preview", JOptionPane.ERROR_MESSAGE);
217 | return;
218 | }
219 | proofer.setImage(file, bi);
220 | updateSourceButtonUI();
221 | }
222 |
223 | private void switchSourceType(String sourceType) {
224 | if (sourceType.equals(proofer.getSourceType())) {
225 | return;
226 | }
227 |
228 | proofer.setSourceType(sourceType);
229 |
230 | if (!Proofer.SOURCE_TYPE_SCREEN.equals(sourceType)) {
231 | regionSelector.showWindow(false);
232 | }
233 |
234 | updateSourceButtonUI();
235 | }
236 |
237 | private void trySaveFrameConfig() {
238 | try {
239 | Properties props = new Properties();
240 | props.setProperty("x", String.valueOf(frame.getX()));
241 | props.setProperty("y", String.valueOf(frame.getY()));
242 | props.storeToXML(new FileOutputStream(
243 | new File(Util.getCacheDirectory(), "config.xml")), null);
244 | } catch (IOException e) {
245 | e.printStackTrace();
246 | }
247 | }
248 |
249 | private void tryLoadFrameConfig() {
250 | try {
251 | Properties props = new Properties();
252 | props.loadFromXML(
253 | new FileInputStream(new File(Util.getCacheDirectory(), "config.xml")));
254 | frame.setLocation(
255 | Integer.parseInt(props.getProperty("x", String.valueOf(frame.getX()))),
256 | Integer.parseInt(props.getProperty("y", String.valueOf(frame.getY()))));
257 | } catch (FileNotFoundException ignored) {
258 | } catch (IOException e) {
259 | e.printStackTrace();
260 | }
261 | }
262 |
263 | public void windowClosing(WindowEvent windowEvent) {
264 | onQuit();
265 | }
266 |
267 | public void onQuit() {
268 | try {
269 | proofer.killAndroidApp();
270 | } catch (ProoferException e) {
271 | e.printStackTrace();
272 | }
273 |
274 | trySaveFrameConfig();
275 | frame.dispose();
276 | System.exit(0);
277 | }
278 |
279 | public void windowOpened(WindowEvent windowEvent) {
280 | }
281 |
282 | public void windowClosed(WindowEvent windowEvent) {
283 | }
284 |
285 | public void windowIconified(WindowEvent windowEvent) {
286 | }
287 |
288 | public void windowDeiconified(WindowEvent windowEvent) {
289 | }
290 |
291 | public void windowActivated(WindowEvent windowEvent) {
292 | }
293 |
294 | public void windowDeactivated(WindowEvent windowEvent) {
295 | }
296 |
297 | public void onStateChange(Proofer.State newState) {
298 | switch (newState) {
299 | case ConnectedActive:
300 | statusLabel.setText("Connected, active");
301 | break;
302 | case ConnectedIdle:
303 | statusLabel.setText("Connected, inactive");
304 | break;
305 | case Disconnected:
306 | statusLabel.setText("Disconnected");
307 | break;
308 | case Unknown:
309 | statusLabel.setText("N/A");
310 | break;
311 | }
312 | }
313 |
314 | public void onDeviceSizeChanged(Dimension size) {
315 | regionSelector.requestDeviceSize(size);
316 | }
317 |
318 | public void onRegionChanged(Rectangle region) {
319 | if (proofer != null) {
320 | proofer.setRequestedSourceRegion(region);
321 | }
322 | }
323 |
324 | @Override
325 | public void onRegionWindowVisibilityChanged(boolean visible) {
326 | updateSourceButtonUI();
327 | }
328 |
329 | private void updateSourceButtonUI() {
330 | String sourceType = proofer.getSourceType();
331 | if (Proofer.SOURCE_TYPE_FILE.equals(sourceType)) {
332 | File currentFile = proofer.getFile();
333 | if (currentFile != null) {
334 | sourceButton.setText(currentFile.getName());
335 | } else {
336 | sourceButton.setText("Choose File");
337 | sourceButton.setMnemonic('C');
338 | }
339 |
340 | } else if (Proofer.SOURCE_TYPE_SCREEN.equals(sourceType)) {
341 | if (regionSelector.isVisible()) {
342 | sourceButton.setText("Close Mirror Region Window");
343 | } else {
344 | sourceButton.setText("Select Mirror Region");
345 | }
346 | sourceButton.setMnemonic('M');
347 | }
348 | }
349 |
350 | {
351 | // GUI initializer generated by IntelliJ IDEA GUI Designer
352 | // >>> IMPORTANT!! <<<
353 | // DO NOT EDIT OR ADD ANY CODE HERE!
354 | $$$setupUI$$$();
355 | }
356 |
357 | /**
358 | * Method generated by IntelliJ IDEA GUI Designer >>> IMPORTANT!! <<< DO NOT edit this method OR
359 | * call it in your code!
360 | *
361 | * @noinspection ALL
362 | */
363 | private void $$$setupUI$$$() {
364 | contentPanel = new JPanel();
365 | contentPanel.setLayout(new GridBagLayout());
366 | reinstallButton = new JButton();
367 | reinstallButton.setText("Re-install App");
368 | reinstallButton.setMnemonic('R');
369 | reinstallButton.setDisplayedMnemonicIndex(0);
370 | GridBagConstraints gbc;
371 | gbc = new GridBagConstraints();
372 | gbc.gridx = 0;
373 | gbc.gridy = 5;
374 | gbc.gridwidth = 3;
375 | gbc.weighty = 1.0;
376 | gbc.fill = GridBagConstraints.HORIZONTAL;
377 | gbc.insets = new Insets(0, 8, 8, 8);
378 | contentPanel.add(reinstallButton, gbc);
379 | sourceButton = new JButton();
380 | sourceButton.setText("Select Mirror Region");
381 | sourceButton.setMnemonic('M');
382 | sourceButton.setDisplayedMnemonicIndex(7);
383 | gbc = new GridBagConstraints();
384 | gbc.gridx = 0;
385 | gbc.gridy = 3;
386 | gbc.gridwidth = 3;
387 | gbc.weighty = 1.0;
388 | gbc.fill = GridBagConstraints.HORIZONTAL;
389 | gbc.insets = new Insets(0, 8, 0, 8);
390 | contentPanel.add(sourceButton, gbc);
391 | final JSeparator separator1 = new JSeparator();
392 | gbc = new GridBagConstraints();
393 | gbc.gridx = 0;
394 | gbc.gridy = 4;
395 | gbc.gridwidth = 3;
396 | gbc.fill = GridBagConstraints.BOTH;
397 | gbc.insets = new Insets(4, 0, 4, 0);
398 | contentPanel.add(separator1, gbc);
399 | final JSeparator separator2 = new JSeparator();
400 | gbc = new GridBagConstraints();
401 | gbc.gridx = 0;
402 | gbc.gridy = 1;
403 | gbc.gridwidth = 3;
404 | gbc.fill = GridBagConstraints.BOTH;
405 | gbc.insets = new Insets(4, 0, 4, 0);
406 | contentPanel.add(separator2, gbc);
407 | screenCaptureSourceButton = new JRadioButton();
408 | screenCaptureSourceButton.setSelected(true);
409 | screenCaptureSourceButton.setText("Screen");
410 | screenCaptureSourceButton.setMnemonic('S');
411 | screenCaptureSourceButton.setDisplayedMnemonicIndex(0);
412 | gbc = new GridBagConstraints();
413 | gbc.gridx = 1;
414 | gbc.gridy = 2;
415 | gbc.fill = GridBagConstraints.HORIZONTAL;
416 | gbc.insets = new Insets(0, 0, 4, 0);
417 | contentPanel.add(screenCaptureSourceButton, gbc);
418 | localFileSourceButton = new JRadioButton();
419 | localFileSourceButton.setText("File");
420 | localFileSourceButton.setMnemonic('F');
421 | localFileSourceButton.setDisplayedMnemonicIndex(0);
422 | gbc = new GridBagConstraints();
423 | gbc.gridx = 2;
424 | gbc.gridy = 2;
425 | gbc.weightx = 1.0;
426 | gbc.fill = GridBagConstraints.HORIZONTAL;
427 | gbc.insets = new Insets(0, 0, 4, 8);
428 | contentPanel.add(localFileSourceButton, gbc);
429 | final JLabel label1 = new JLabel();
430 | label1.setForeground(new Color(-10066330));
431 | label1.setText("Source:");
432 | gbc = new GridBagConstraints();
433 | gbc.gridx = 0;
434 | gbc.gridy = 2;
435 | gbc.insets = new Insets(0, 8, 4, 4);
436 | contentPanel.add(label1, gbc);
437 | final JLabel label2 = new JLabel();
438 | label2.setForeground(new Color(-10066330));
439 | label2.setText("Status:");
440 | gbc = new GridBagConstraints();
441 | gbc.gridx = 0;
442 | gbc.gridy = 0;
443 | gbc.anchor = GridBagConstraints.WEST;
444 | gbc.insets = new Insets(8, 8, 0, 4);
445 | contentPanel.add(label2, gbc);
446 | statusLabel = new JLabel();
447 | statusLabel.setFont(new Font(statusLabel.getFont().getName(), Font.BOLD,
448 | statusLabel.getFont().getSize()));
449 | statusLabel.setHorizontalAlignment(0);
450 | statusLabel.setHorizontalTextPosition(11);
451 | statusLabel.setText("N/A");
452 | gbc = new GridBagConstraints();
453 | gbc.gridx = 1;
454 | gbc.gridy = 0;
455 | gbc.gridwidth = 2;
456 | gbc.weightx = 1.0;
457 | gbc.fill = GridBagConstraints.HORIZONTAL;
458 | gbc.insets = new Insets(8, 0, 0, 0);
459 | contentPanel.add(statusLabel, gbc);
460 | ButtonGroup buttonGroup;
461 | buttonGroup = new ButtonGroup();
462 | buttonGroup.add(localFileSourceButton);
463 | buttonGroup.add(screenCaptureSourceButton);
464 | buttonGroup.add(localFileSourceButton);
465 | }
466 |
467 | /**
468 | * @noinspection ALL
469 | */
470 | public JComponent $$$getRootComponent$$$() {
471 | return contentPanel;
472 | }
473 | }
474 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/Proofer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer;
18 |
19 | import com.google.android.desktop.proofer.os.OSBinder;
20 |
21 | import java.awt.*;
22 | import java.awt.geom.Rectangle2D;
23 | import java.awt.image.BufferedImage;
24 | import java.io.BufferedOutputStream;
25 | import java.io.ByteArrayOutputStream;
26 | import java.io.DataInputStream;
27 | import java.io.File;
28 | import java.io.IOException;
29 | import java.net.Socket;
30 |
31 | import javax.imageio.ImageIO;
32 |
33 | public class Proofer {
34 | public static final String SOURCE_TYPE_FILE = "file";
35 | public static final String SOURCE_TYPE_SCREEN = "screen";
36 |
37 | private boolean debug = Util.isDebug();
38 |
39 | private AdbRunner adbRunner;
40 | private ProoferClient client;
41 |
42 | private String sourceType = SOURCE_TYPE_SCREEN;
43 | private File file;
44 | private State state = State.Unknown;
45 | private ProoferCallbacks prooferCallbacks;
46 |
47 | public static interface ProoferCallbacks {
48 | public void onStateChange(State newState);
49 | public void onDeviceSizeChanged(Dimension size);
50 | }
51 |
52 | public static enum State {
53 | ConnectedActive,
54 | ConnectedIdle,
55 | Disconnected,
56 | Unknown,
57 | }
58 |
59 | public Proofer(ProoferCallbacks prooferCallbacks) {
60 | this.adbRunner = new AdbRunner();
61 | this.client = new ProoferClient();
62 | this.prooferCallbacks = prooferCallbacks;
63 | }
64 |
65 | public void startConnectionLoop() {
66 | new Thread(new Runnable() {
67 | public void run() {
68 | while (true) {
69 | try {
70 | client.connectAndWaitForRequests();
71 | } catch (CannotConnectException e) {
72 | // Can't connect to device, try re-setting up port forwarding.
73 | // If no devices are connected, this will fail.
74 | try {
75 | setupPortForwarding();
76 | } catch (ProoferException e2) {
77 | // If we get an error here, we're disconnected.
78 | updateState(State.Disconnected);
79 | }
80 | }
81 |
82 | try {
83 | Thread.sleep(1000);
84 | } catch (InterruptedException e) {
85 | break;
86 | }
87 | }
88 | }
89 | }).start();
90 | }
91 |
92 | public void runAndroidApp() throws ProoferException {
93 | adbRunner.adb(new String[]{
94 | "shell", "am", "start",
95 | "-a", "android.intent.action.MAIN",
96 | "-c", "android.intent.category.LAUNCHER",
97 | "-n", Config.ANDROID_APP_PACKAGE_NAME + "/.DesktopViewerActivity"
98 | });
99 | }
100 |
101 | public void killAndroidApp() throws ProoferException {
102 | adbRunner.adb(new String[]{
103 | "shell", "am", "force-stop",
104 | Config.ANDROID_APP_PACKAGE_NAME
105 | });
106 | }
107 |
108 | public void uninstallAndroidApp() throws ProoferException {
109 | adbRunner.adb(new String[]{
110 | "uninstall", Config.ANDROID_APP_PACKAGE_NAME
111 | });
112 | }
113 |
114 | public void installAndroidApp(boolean force) throws ProoferException {
115 | if (force || !isAndroidAppInstalled()) {
116 | File apkPath = new File(Util.getCacheDirectory(), "Proofer.apk");
117 | if (Util.extractResource("assets/Proofer.apk", apkPath)) {
118 | adbRunner.adb(new String[]{
119 | "install", "-r", apkPath.toString()
120 | });
121 | } else {
122 | throw new ProoferException("Error extracting Android APK.");
123 | }
124 | }
125 | }
126 |
127 | public void setupPortForwarding() throws ProoferException {
128 | try {
129 | adbRunner.adb(new String[]{
130 | "forward", "tcp:" + Config.PORT_LOCAL, "tcp:" + Config.PORT_DEVICE
131 | });
132 | } catch (ProoferException e) {
133 | throw new ProoferException("Couldn't automatically setup port forwarding. "
134 | + "You'll need to "
135 | + "manually run "
136 | + "\"adb forward tcp:" + Config.PORT_LOCAL + " "
137 | + "tcp:" + Config.PORT_DEVICE + "\" "
138 | + "on the command line.", e);
139 | }
140 | }
141 |
142 | public boolean isAndroidAppInstalled() throws ProoferException {
143 | String out = adbRunner.adb(new String[]{
144 | "shell", "pm", "list", "packages"
145 | });
146 | return out.contains(Config.ANDROID_APP_PACKAGE_NAME);
147 | }
148 |
149 | public void setSourceType(String sourceType) {
150 | this.sourceType = sourceType;
151 | }
152 |
153 | public String getSourceType() {
154 | return sourceType;
155 | }
156 |
157 | public File getFile() {
158 | return file;
159 | }
160 |
161 | public void setRequestedSourceRegion(Rectangle region) {
162 | client.setRequestedSourceRegion(region);
163 | }
164 |
165 | public void setImage(File file, BufferedImage image) {
166 | this.file = file;
167 | client.setImage(image);
168 | }
169 |
170 | private void updateState(State newState) {
171 | if (this.state != newState && debug) {
172 | switch (newState) {
173 | case ConnectedActive:
174 | System.out.println("State: Connected and active");
175 | break;
176 | case ConnectedIdle:
177 | System.out.println("State: Connected and idle");
178 | break;
179 | case Disconnected:
180 | System.out.println("State: Disconnected");
181 | break;
182 | }
183 | }
184 |
185 | if (this.state != newState && prooferCallbacks != null) {
186 | prooferCallbacks.onStateChange(newState);
187 | }
188 |
189 | this.state = newState;
190 | }
191 |
192 | public static class CannotConnectException extends ProoferException {
193 | public CannotConnectException(Throwable throwable) {
194 | super(throwable);
195 | }
196 | }
197 |
198 | private class ProoferClient {
199 | private Rectangle requestedSourceRegion = new Rectangle(0, 0, 0, 0);
200 | private BufferedImage forcedImage;
201 | private Robot robot;
202 | private Rectangle screenBounds;
203 | private Dimension currentDeviceSize = new Dimension();
204 |
205 | public ProoferClient() {
206 | try {
207 | this.robot = new Robot();
208 | } catch (AWTException e) {
209 | System.err.println("Error getting robot.");
210 | e.printStackTrace();
211 | System.exit(1);
212 | }
213 |
214 | GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
215 | GraphicsDevice[] screenDevices = environment.getScreenDevices();
216 |
217 | Rectangle2D tempBounds = new Rectangle();
218 | for (GraphicsDevice screenDevice : screenDevices) {
219 | tempBounds = tempBounds.createUnion(
220 | screenDevice.getDefaultConfiguration().getBounds());
221 | }
222 | screenBounds = tempBounds.getBounds();
223 | }
224 |
225 | public void setRequestedSourceRegion(Rectangle region) {
226 | requestedSourceRegion = region;
227 | }
228 |
229 | public void setImage(BufferedImage image) {
230 | this.forcedImage = image;
231 | }
232 |
233 | public void connectAndWaitForRequests() throws CannotConnectException {
234 | Socket socket;
235 |
236 | // Establish the connection.
237 | try {
238 | socket = new Socket("localhost", Config.PORT_LOCAL);
239 | } catch (IOException e) {
240 | throw new CannotConnectException(e);
241 | }
242 |
243 | if (debug) {
244 | System.out.println(
245 | "Local socket established " + socket.getRemoteSocketAddress().toString());
246 | }
247 |
248 | // Wait for requests.
249 | try {
250 | DataInputStream dis = new DataInputStream(socket.getInputStream());
251 | BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
252 | Dimension deviceSize = new Dimension();
253 |
254 | while (true) {
255 | // Try processing a request.
256 | dis.readInt(); // unused x
257 | dis.readInt(); // unused y
258 |
259 | deviceSize.width = dis.readInt();
260 | deviceSize.height = dis.readInt();
261 |
262 | // If we reach this point, we didn't hit an IOException and we've received
263 | // a request from the device.
264 |
265 | if (!deviceSize.equals(currentDeviceSize) && prooferCallbacks != null) {
266 | prooferCallbacks.onDeviceSizeChanged(deviceSize);
267 | if (debug) {
268 | System.out.println("Got device size: " + currentDeviceSize.width
269 | + "x" + currentDeviceSize.height);
270 | }
271 | currentDeviceSize = new Dimension(deviceSize);
272 | }
273 |
274 | updateState(State.ConnectedActive);
275 |
276 | if (deviceSize.width > 1 && deviceSize.height > 1) {
277 | BufferedImage bi;
278 | if (SOURCE_TYPE_FILE.equals(sourceType)) {
279 | bi = forcedImage;
280 | } else {
281 | bi = capture();
282 | }
283 |
284 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
285 |
286 | if (bi != null) {
287 | if (bi.getWidth() != currentDeviceSize.width ||
288 | bi.getHeight() != currentDeviceSize.height) {
289 | // Scale the bitmap
290 | BufferedImage resized = new BufferedImage(
291 | currentDeviceSize.width,
292 | currentDeviceSize.height,
293 | bi.getType());
294 | Graphics2D g2d = resized.createGraphics();
295 | g2d.setRenderingHint(
296 | RenderingHints.KEY_INTERPOLATION,
297 | RenderingHints.VALUE_INTERPOLATION_BILINEAR);
298 | g2d.drawImage(
299 | bi,
300 | 0, 0, currentDeviceSize.width, currentDeviceSize.height,
301 | 0, 0, bi.getWidth(), bi.getHeight(),
302 | null);
303 | g2d.dispose();
304 | bi = resized;
305 | }
306 |
307 | ImageIO.write(bi, "PNG", baos);
308 | } else {
309 | baos.write(new byte[]{0});
310 | }
311 |
312 | byte[] out = baos.toByteArray();
313 | int len = out.length;
314 | byte[] outlen = new byte[4];
315 | outlen[0] = (byte) ((len >> 24) & 0xFF);
316 | outlen[1] = (byte) ((len >> 16) & 0xFF);
317 | outlen[2] = (byte) ((len >> 8) & 0xFF);
318 | outlen[3] = (byte) (len & 0xFF);
319 |
320 | if (debug) {
321 | System.out.println("Writing " + len + " bytes.");
322 | }
323 |
324 | bos.write(outlen, 0, 4);
325 | bos.write(out, 0, len);
326 | bos.flush();
327 | }
328 |
329 | // This loop will exit only when an IOException is thrown, indicating there's
330 | // nothing further to read.
331 | }
332 | } catch (IOException e) {
333 | // If we're not "connected", this just means we haven't received any requests yet
334 | // on the socket, so there's no error to log.
335 | if (debug) {
336 | System.out.println("No activity.");
337 | }
338 | }
339 |
340 | // No (or no more) requests.
341 | updateState(State.ConnectedIdle);
342 | }
343 |
344 | private BufferedImage capture() {
345 | Rectangle captureRect = new Rectangle(
346 | Math.max(screenBounds.x, requestedSourceRegion.x),
347 | Math.max(screenBounds.y, requestedSourceRegion.y),
348 | requestedSourceRegion.width,
349 | requestedSourceRegion.height);
350 |
351 | if (captureRect.x + captureRect.width > screenBounds.x + screenBounds.width) {
352 | captureRect.x = screenBounds.x + screenBounds.width - captureRect.width;
353 | }
354 |
355 | if (captureRect.y + captureRect.height > screenBounds.y + screenBounds.height) {
356 | captureRect.y = screenBounds.y + screenBounds.height - captureRect.height;
357 | }
358 |
359 | long before = System.currentTimeMillis();
360 | BufferedImage bi = robot.createScreenCapture(captureRect);
361 | long after = System.currentTimeMillis();
362 |
363 | if (debug) {
364 | System.out.println("Capture time: " + (after - before) + " msec");
365 | }
366 |
367 | return bi;
368 | }
369 | }
370 | }
371 |
372 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/ProoferException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer;
18 |
19 | public class ProoferException extends Exception {
20 | public ProoferException() {
21 | super();
22 | }
23 |
24 | public ProoferException(String s) {
25 | super(s);
26 | }
27 |
28 | public ProoferException(String s, Throwable throwable) {
29 | super(s, throwable);
30 | }
31 |
32 | public ProoferException(Throwable throwable) {
33 | super(throwable);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/RegionSelector.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer;
18 |
19 | import com.google.android.desktop.proofer.os.OSBinder;
20 |
21 | import com.sun.awt.AWTUtilities;
22 |
23 | import java.awt.*;
24 | import java.awt.event.KeyEvent;
25 | import java.awt.event.KeyListener;
26 | import java.awt.event.MouseEvent;
27 | import java.awt.event.MouseListener;
28 | import java.awt.event.MouseMotionListener;
29 | import java.awt.geom.Rectangle2D;
30 | import java.io.File;
31 | import java.io.FileInputStream;
32 | import java.io.FileNotFoundException;
33 | import java.io.FileOutputStream;
34 | import java.io.IOException;
35 | import java.util.Properties;
36 | import java.util.concurrent.Executors;
37 | import java.util.concurrent.ScheduledExecutorService;
38 | import java.util.concurrent.ScheduledFuture;
39 | import java.util.concurrent.TimeUnit;
40 |
41 | import javax.swing.*;
42 |
43 | public class RegionSelector {
44 | private RegionSelectorFrame frame;
45 | private OSBinder osBinder;
46 | private float displayScaleFactor;
47 |
48 | private Rectangle region = new Rectangle(100, 100, 480, 800);
49 | private Dimension deviceSize = new Dimension(480, 800);
50 | private RegionChangeCallback regionChangeCallback;
51 |
52 | public static interface RegionChangeCallback {
53 | public void onRegionChanged(Rectangle region);
54 | public void onRegionWindowVisibilityChanged(boolean visible);
55 | }
56 |
57 | public RegionSelector(RegionChangeCallback regionChangeCallback) {
58 | this.regionChangeCallback = regionChangeCallback;
59 | osBinder = OSBinder.getBinder(null);
60 | displayScaleFactor = osBinder.getDisplayScaleFactor();
61 | setupUI();
62 | }
63 |
64 | private void setupUI() {
65 | // http://java.sun.com/developer/technicalArticles/GUI/translucent_shaped_windows/
66 |
67 | try {
68 | if (!AWTUtilities.isTranslucencySupported(AWTUtilities.Translucency.TRANSLUCENT)) {
69 | throw new UnsupportedOperationException();
70 | }
71 |
72 | //perform translucency operations here
73 | GraphicsEnvironment env =
74 | GraphicsEnvironment.getLocalGraphicsEnvironment();
75 | GraphicsDevice[] devices = env.getScreenDevices();
76 | GraphicsConfiguration translucencyCapableGC = null;
77 | for (int i = 0; i < devices.length && translucencyCapableGC == null; i++) {
78 | GraphicsConfiguration[] configs = devices[i].getConfigurations();
79 | for (int j = 0; j < configs.length && translucencyCapableGC == null; j++) {
80 | if (AWTUtilities.isTranslucencyCapable(configs[j])) {
81 | translucencyCapableGC = configs[j];
82 | }
83 | }
84 | }
85 |
86 | frame = new RegionSelectorFrame(translucencyCapableGC);
87 | frame.setUndecorated(true);
88 | AWTUtilities.setWindowOpaque(frame, false);
89 | frame.getRootPane().putClientProperty("apple.awt.draggableWindowBackground",
90 | Boolean.FALSE);
91 | } catch (NoClassDefFoundError e) {
92 | frame = new RegionSelectorFrame(null);
93 | frame.setUndecorated(true);
94 | } catch (UnsupportedOperationException e) {
95 | frame = new RegionSelectorFrame(null);
96 | frame.setUndecorated(true);
97 | }
98 |
99 | frame.setAlwaysOnTop(true);
100 | frame.setResizable(true);
101 |
102 | frame.setBounds(region);
103 | tryLoadFrameConfig();
104 | region = frame.getBounds();
105 | }
106 |
107 | public boolean isVisible() {
108 | return frame.isVisible();
109 | }
110 |
111 | public void showWindow(boolean show) {
112 | frame.setVisible(show);
113 | regionChangeCallback.onRegionWindowVisibilityChanged(show);
114 | }
115 |
116 | public Rectangle getRegion() {
117 | return region;
118 | }
119 |
120 | private void setRegion(Rectangle region) {
121 | this.region = region;
122 | this.frame.setLocation(region.getLocation());
123 | this.frame.setSize(region.getSize());
124 | if (regionChangeCallback != null) {
125 | regionChangeCallback.onRegionChanged(region);
126 | }
127 | }
128 |
129 | public void requestDeviceSize(Dimension size) {
130 | double currentScale = region.getWidth() / deviceSize.getWidth();
131 | Dimension scaledSize = new Dimension(
132 | (int) (size.width * currentScale),
133 | (int) (size.height * currentScale));
134 |
135 | deviceSize = new Dimension(size);
136 | region.setSize(scaledSize);
137 | this.frame.setSize(scaledSize);
138 | }
139 |
140 | void trySaveFrameConfig() {
141 | try {
142 | Properties props = new Properties();
143 | props.setProperty("x", String.valueOf(frame.getX()));
144 | props.setProperty("y", String.valueOf(frame.getY()));
145 | props.setProperty("scale", String.valueOf(
146 | frame.getWidth() * 1f / deviceSize.getWidth() * displayScaleFactor));
147 | props.storeToXML(new FileOutputStream(
148 | new File(Util.getCacheDirectory(), "region.xml")), null);
149 | } catch (IOException e) {
150 | e.printStackTrace();
151 | }
152 | }
153 |
154 | private final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
155 |
156 | private Runnable saveFrameConfigRunnable = new Runnable() {
157 | @Override
158 | public void run() {
159 | trySaveFrameConfig();
160 | }
161 | };
162 |
163 | private ScheduledFuture> saveFrameConfigScheduleHandle;
164 |
165 | void delayedTrySaveFrameConfig() {
166 | if (saveFrameConfigScheduleHandle != null) {
167 | saveFrameConfigScheduleHandle.cancel(false);
168 | }
169 |
170 | saveFrameConfigScheduleHandle = worker
171 | .schedule(saveFrameConfigRunnable, 1, TimeUnit.SECONDS);
172 | }
173 |
174 | void tryLoadFrameConfig() {
175 | try {
176 | Properties props = new Properties();
177 | props.loadFromXML(
178 | new FileInputStream(new File(Util.getCacheDirectory(), "region.xml")));
179 | frame.setLocation(
180 | Integer.parseInt(props.getProperty("x", String.valueOf(frame.getX()))),
181 | Integer.parseInt(props.getProperty("y", String.valueOf(frame.getY()))));
182 | double scale = Double.parseDouble(props.getProperty("scale", "1"));
183 | this.frame.setSize(
184 | (int) (scale / displayScaleFactor * deviceSize.width),
185 | (int) (scale / displayScaleFactor * deviceSize.height));
186 | } catch (FileNotFoundException ignored) {
187 | } catch (IOException e) {
188 | e.printStackTrace();
189 | }
190 | }
191 |
192 | private class RegionSelectorFrame extends JFrame implements
193 | MouseListener, MouseMotionListener, KeyListener {
194 | private static final int LINE_SPACING_PIXELS = 8;
195 |
196 | private static final int RESIZE_GRIP_SIZE_PIXELS = 20;
197 |
198 | private static final int HIT_TEST_HAS_N = 0x1;
199 | private static final int HIT_TEST_HAS_S = 0x2;
200 | private static final int HIT_TEST_HAS_E = 0x10;
201 | private static final int HIT_TEST_HAS_W = 0x20;
202 |
203 | private static final int HIT_TEST_NONE = 0;
204 | private static final int HIT_TEST_NE = HIT_TEST_HAS_N | HIT_TEST_HAS_E;
205 | private static final int HIT_TEST_SE = HIT_TEST_HAS_S | HIT_TEST_HAS_E;
206 | private static final int HIT_TEST_NW = HIT_TEST_HAS_N | HIT_TEST_HAS_W;
207 | private static final int HIT_TEST_SW = HIT_TEST_HAS_S | HIT_TEST_HAS_W;
208 |
209 | private Paint strokePaint;
210 | private Paint fillPaint;
211 | private Stroke stroke;
212 | private Font fontTitle;
213 | private Font fontSubtitle;
214 |
215 | private Point startDragPoint;
216 | private Point startLocation;
217 | private Dimension startSize;
218 | private int startHitTest;
219 |
220 | private RegionSelectorFrame(GraphicsConfiguration graphicsConfiguration) {
221 | super(graphicsConfiguration);
222 | fillPaint = new Color(0, 0, 0, 32);
223 | strokePaint = new Color(255, 0, 0, 128);
224 | stroke = new BasicStroke(5, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER);
225 | fontTitle = new Font(Font.DIALOG, Font.BOLD, 30);
226 | fontSubtitle = new Font(Font.DIALOG, Font.BOLD, 16);
227 |
228 | addMouseListener(this);
229 | addMouseMotionListener(this);
230 | addKeyListener(this);
231 | }
232 |
233 | @Override
234 | public void paint(Graphics graphics) {
235 | if (graphics instanceof Graphics2D) {
236 | Graphics2D g2d = (Graphics2D) graphics;
237 | g2d.clearRect(0, 0, getWidth(), getHeight());
238 | g2d.setPaint(fillPaint);
239 | g2d.fillRect(0, 0, getWidth(), getHeight());
240 |
241 | g2d.setPaint(strokePaint);
242 | g2d.setStroke(stroke);
243 | g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
244 |
245 | int y = getHeight() / 2;
246 | // Line 1
247 | g2d.setFont(fontTitle);
248 | String s = deviceSize.width
249 | + "\u00d7"
250 | + deviceSize.height;
251 | Rectangle2D r = fontTitle.getStringBounds(s, g2d.getFontRenderContext());
252 | g2d.drawString(s, (int) (getWidth() - r.getWidth()) / 2, y);
253 |
254 | // Line 2
255 | g2d.setFont(fontSubtitle);
256 | int scaledWidth = (int) (getWidth() * displayScaleFactor);
257 | if (scaledWidth != deviceSize.width) {
258 | s = "(" + (100 * scaledWidth / deviceSize.width) + "%)";
259 | } else {
260 | s = "(Drag corners to resize)";
261 | }
262 | r = fontSubtitle.getStringBounds(s, g2d.getFontRenderContext());
263 | y += r.getHeight() + LINE_SPACING_PIXELS;
264 | g2d.drawString(s, (int) (getWidth() - r.getWidth()) / 2, y);
265 |
266 | // Line 3
267 | s = "(Double-click or ESC to hide)";
268 | r = fontSubtitle.getStringBounds(s, g2d.getFontRenderContext());
269 | y += r.getHeight() + LINE_SPACING_PIXELS;
270 | g2d.drawString(s, (int) (getWidth() - r.getWidth()) / 2, y);
271 |
272 | } else {
273 | super.paint(graphics);
274 | }
275 | }
276 |
277 | // http://www.java2s.com/Tutorial/Java/0240__Swing/Dragandmoveaframefromitscontentarea.htm
278 |
279 | public void mousePressed(MouseEvent mouseEvent) {
280 | startHitTest = hitTest(mouseEvent.getX(), mouseEvent.getY());
281 | startDragPoint = getScreenLocation(mouseEvent);
282 | startLocation = getLocation();
283 | startSize = getSize();
284 |
285 | if (mouseEvent.getClickCount() == 2) {
286 | showWindow(false);
287 | }
288 | }
289 |
290 | public void mouseDragged(MouseEvent mouseEvent) {
291 | Point current = getScreenLocation(mouseEvent);
292 |
293 | if (startHitTest == HIT_TEST_NONE) {
294 | // Moving
295 | Point newLocation = new Point(
296 | startLocation.x + current.x - startDragPoint.x,
297 | startLocation.y + current.y - startDragPoint.y);
298 | region.setLocation(newLocation);
299 | setRegion(region);
300 |
301 | } else {
302 | // Resizing. Maintain aspect ratio
303 | boolean n = (startHitTest & HIT_TEST_HAS_N) != 0;
304 | boolean w = (startHitTest & HIT_TEST_HAS_W) != 0;
305 |
306 | Dimension newSize = new Dimension(
307 | startSize.width + (w ? -1 : 1) * (current.x - startDragPoint.x),
308 | startSize.height + (n ? -1 : 1) * (current.y - startDragPoint.y));
309 | int keepAspectWidth = (int) (deviceSize.width * newSize.getHeight()
310 | / deviceSize.height);
311 | int keepAspectHeight = (int) (deviceSize.height * newSize.getWidth()
312 | / deviceSize.width);
313 |
314 | if (keepAspectHeight <= newSize.height) {
315 | newSize.height = keepAspectHeight;
316 | } else {
317 | newSize.width = keepAspectWidth;
318 | }
319 |
320 | // Lock to 25% increments (or possibly 33% increments)
321 | float naturalFrac = (float) (newSize.height / deviceSize.getHeight());
322 | float frac = Math.round(naturalFrac * 4 * displayScaleFactor) / (4f * displayScaleFactor);
323 | float frac3 = Math.round(naturalFrac * 3 * displayScaleFactor) / (3f * displayScaleFactor);
324 | frac = (Math.abs(naturalFrac - frac3) < Math.abs(naturalFrac - frac))
325 | ? frac3 : frac;
326 | frac = Math.max(0.25f / displayScaleFactor, frac);
327 | newSize.width = (int) (deviceSize.getWidth() * frac);
328 | newSize.height = (int) (deviceSize.getHeight() * frac);
329 |
330 | Point newLocation = new Point(
331 | w ? (startLocation.x - newSize.width + startSize.width) : startLocation.x,
332 | n ? (startLocation.y - newSize.height + startSize.height) : startLocation.y);
333 |
334 | setRegion(new Rectangle(newLocation, newSize));
335 | }
336 |
337 | delayedTrySaveFrameConfig();
338 | }
339 |
340 | private Point getScreenLocation(MouseEvent e) {
341 | Point cursor = e.getPoint();
342 | Point targetLocation = getLocationOnScreen();
343 | return new Point(
344 | (int) (targetLocation.getX() + cursor.getX()),
345 | (int) (targetLocation.getY() + cursor.getY()));
346 | }
347 |
348 | private int hitTest(int x, int y) {
349 | if (x <= RESIZE_GRIP_SIZE_PIXELS) {
350 | if (y <= RESIZE_GRIP_SIZE_PIXELS) {
351 | return HIT_TEST_NW;
352 | } else if (y >= getHeight() - RESIZE_GRIP_SIZE_PIXELS) {
353 | return HIT_TEST_SW;
354 | }
355 | } else if (x >= getWidth() - RESIZE_GRIP_SIZE_PIXELS) {
356 | if (y <= RESIZE_GRIP_SIZE_PIXELS) {
357 | return HIT_TEST_NE;
358 | } else if (y >= getHeight() - RESIZE_GRIP_SIZE_PIXELS) {
359 | return HIT_TEST_SE;
360 | }
361 | }
362 | return HIT_TEST_NONE;
363 | }
364 |
365 | public void mouseMoved(MouseEvent mouseEvent) {
366 | switch (hitTest(mouseEvent.getX(), mouseEvent.getY())) {
367 | case HIT_TEST_NE:
368 | setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
369 | break;
370 | case HIT_TEST_NW:
371 | setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
372 | break;
373 | case HIT_TEST_SE:
374 | setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
375 | break;
376 | case HIT_TEST_SW:
377 | setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
378 | break;
379 | default:
380 | setCursor(Cursor.getDefaultCursor());
381 | }
382 | }
383 |
384 | public void mouseClicked(MouseEvent mouseEvent) {
385 | }
386 |
387 | public void mouseReleased(MouseEvent mouseEvent) {
388 | }
389 |
390 | public void mouseEntered(MouseEvent mouseEvent) {
391 | }
392 |
393 | public void mouseExited(MouseEvent mouseEvent) {
394 | }
395 |
396 | public void keyTyped(KeyEvent keyEvent) {
397 | }
398 |
399 | public void keyPressed(KeyEvent keyEvent) {
400 | if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
401 | showWindow(false);
402 | return;
403 | }
404 |
405 | Point newLocation = getLocation();
406 |
407 | int val = keyEvent.isShiftDown() ? 10 : 1;
408 |
409 | switch (keyEvent.getKeyCode()) {
410 | case KeyEvent.VK_UP:
411 | newLocation.y -= val;
412 | break;
413 | case KeyEvent.VK_LEFT:
414 | newLocation.x -= val;
415 | break;
416 | case KeyEvent.VK_DOWN:
417 | newLocation.y += val;
418 | break;
419 | case KeyEvent.VK_RIGHT:
420 | newLocation.x += val;
421 | break;
422 | default:
423 | return;
424 | }
425 |
426 | region.setLocation(newLocation);
427 | setRegion(region);
428 | delayedTrySaveFrameConfig();
429 | }
430 |
431 | public void keyReleased(KeyEvent keyEvent) {
432 | }
433 | }
434 | }
435 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/Util.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer;
18 |
19 | import java.awt.*;
20 | import java.io.BufferedReader;
21 | import java.io.File;
22 | import java.io.FileInputStream;
23 | import java.io.FileNotFoundException;
24 | import java.io.FileOutputStream;
25 | import java.io.IOException;
26 | import java.io.InputStream;
27 | import java.io.InputStreamReader;
28 | import java.io.OutputStream;
29 |
30 | import javax.imageio.ImageIO;
31 |
32 | public class Util {
33 | public static boolean isDebug() {
34 | return "1".equals(System.getenv("PROOFER_DEBUG"));
35 | }
36 |
37 | public static boolean extractResource(String path, File to) {
38 | try {
39 | InputStream in = Util.class.getClassLoader().getResourceAsStream(path);
40 | if (in == null) {
41 | throw new FileNotFoundException(path);
42 | }
43 |
44 | OutputStream out = new FileOutputStream(to);
45 |
46 | byte[] buf = new byte[1024];
47 | int len;
48 | while ((len = in.read(buf)) > 0) {
49 | out.write(buf, 0, len);
50 | }
51 |
52 | in.close();
53 | out.close();
54 | } catch (FileNotFoundException e) {
55 | e.printStackTrace();
56 | return false;
57 | } catch (IOException e) {
58 | e.printStackTrace();
59 | return false;
60 | }
61 | return true;
62 | }
63 |
64 | public static Image[] getAppIconMipmap() {
65 | try {
66 | return new Image[]{
67 | ImageIO.read(
68 | Util.class.getClassLoader().getResourceAsStream("assets/icon_16.png")),
69 | ImageIO.read(
70 | Util.class.getClassLoader().getResourceAsStream("assets/icon_32.png")),
71 | ImageIO.read(
72 | Util.class.getClassLoader().getResourceAsStream("assets/icon_128.png")),
73 | ImageIO.read(
74 | Util.class.getClassLoader().getResourceAsStream("assets/icon_512.png")),
75 | };
76 | } catch (IOException e) {
77 | return new Image[]{};
78 | }
79 | }
80 |
81 | // Cache directory code
82 |
83 | private static File cacheDirectory;
84 |
85 | static {
86 | // Determine/create cache directory
87 |
88 | // Default to root in user's home directory
89 | File rootDir = new File(System.getProperty("user.home"));
90 | if (!rootDir.exists() && rootDir.canWrite()) {
91 | // If not writable, root in current working directory
92 | rootDir = new File(System.getProperty("user.dir"));
93 | if (!rootDir.exists() && rootDir.canWrite()) {
94 | // TODO: create temporary directory somewhere if this fails
95 | System.err.println("No home directory and can't write to current directory.");
96 | System.exit(1);
97 | }
98 | }
99 |
100 | // The actual cache directory will be ${ROOT}/.android/proofer
101 | cacheDirectory = new File(new File(rootDir, ".android"), "proofer");
102 | if (!cacheDirectory.exists()) {
103 | cacheDirectory.mkdirs();
104 | }
105 | cacheDirectory.setWritable(true);
106 | }
107 |
108 | public static int getCacheVersion() {
109 | try {
110 | InputStream in = new FileInputStream(new File(getCacheDirectory(), "cache_version"));
111 | BufferedReader reader = new BufferedReader(new InputStreamReader(in));
112 | return Integer.parseInt(reader.readLine());
113 | } catch (FileNotFoundException e) {
114 | e.printStackTrace();
115 | } catch (IOException e) {
116 | e.printStackTrace();
117 | } catch (NumberFormatException e) {
118 | e.printStackTrace();
119 | }
120 |
121 | return 0;
122 | }
123 |
124 | public static void putCacheVersion(int version) {
125 | try {
126 | OutputStream out = new FileOutputStream(new File(getCacheDirectory(), "cache_version"));
127 | out.write(Integer.toString(version).getBytes());
128 | out.close();
129 | } catch (FileNotFoundException e) {
130 | e.printStackTrace();
131 | } catch (IOException e) {
132 | e.printStackTrace();
133 | }
134 | }
135 |
136 | public static File getCacheDirectory() {
137 | return cacheDirectory;
138 | }
139 |
140 | // Which OS are we running on? (used to unpack different ADB binaries)
141 |
142 | public enum OS {
143 | Windows("windows"),
144 | Linux("linux"),
145 | Mac("mac"),
146 | Other(null);
147 |
148 | public String id;
149 |
150 | OS(String id) {
151 | this.id = id;
152 | }
153 | }
154 |
155 | private static OS currentOS;
156 |
157 | static {
158 | String osName = System.getProperty("os.name", "").toLowerCase();
159 | if (osName.contains("windows")) {
160 | currentOS = OS.Windows;
161 | } else if (osName.contains("linux")) {
162 | currentOS = OS.Linux;
163 | } else if (osName.contains("mac") || osName.contains("darwin")) {
164 | currentOS = OS.Mac;
165 | } else {
166 | currentOS = OS.Other;
167 | }
168 | }
169 |
170 | public static OS getCurrentOS() {
171 | return currentOS;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/os/DefaultBinder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer.os;
18 |
19 | public class DefaultBinder extends OSBinder {
20 | protected DefaultBinder(Callbacks callbacks) {
21 | super(callbacks);
22 | }
23 |
24 | @Override
25 | public float getDisplayScaleFactor() {
26 | return 1f;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/os/MacBinder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer.os;
18 |
19 | import com.apple.eawt.AppEvent;
20 | import com.apple.eawt.Application;
21 | import com.apple.eawt.QuitHandler;
22 | import com.apple.eawt.QuitResponse;
23 | import com.apple.eawt.QuitStrategy;
24 |
25 | import java.awt.*;
26 |
27 | public class MacBinder extends OSBinder implements QuitHandler {
28 | Application application;
29 |
30 | public MacBinder(Callbacks callbacks) {
31 | super(callbacks);
32 |
33 | application = Application.getApplication();
34 | application.setQuitStrategy(QuitStrategy.SYSTEM_EXIT_0);
35 | application.setQuitHandler(this);
36 | }
37 |
38 | @Override
39 | public float getDisplayScaleFactor() {
40 | return (Float) Toolkit.getDefaultToolkit().getDesktopProperty(
41 | "apple.awt.contentScaleFactor");
42 | }
43 |
44 | public void handleQuitRequestWith(AppEvent.QuitEvent quitEvent, QuitResponse quitResponse) {
45 | if (callbacks != null) {
46 | callbacks.onQuit();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/desktop/src/com/google/android/desktop/proofer/os/OSBinder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.desktop.proofer.os;
18 |
19 | import com.google.android.desktop.proofer.Util;
20 |
21 | import java.lang.reflect.InvocationTargetException;
22 |
23 | public abstract class OSBinder {
24 | protected Callbacks callbacks;
25 |
26 | public static interface Callbacks {
27 | public void onQuit();
28 | }
29 |
30 | public static OSBinder getBinder(Callbacks callbacks) {
31 | try {
32 | switch (Util.getCurrentOS()) {
33 | case Mac:
34 | return MacBinder.class.getConstructor(Callbacks.class)
35 | .newInstance(callbacks);
36 | }
37 | } catch (InvocationTargetException e) {
38 | e.printStackTrace();
39 | } catch (NoSuchMethodException e) {
40 | e.printStackTrace();
41 | } catch (InstantiationException e) {
42 | e.printStackTrace();
43 | } catch (IllegalAccessException e) {
44 | e.printStackTrace();
45 | }
46 |
47 | return new DefaultBinder(callbacks);
48 | }
49 |
50 | protected OSBinder(Callbacks callbacks) {
51 | this.callbacks = callbacks;
52 | }
53 |
54 | public abstract float getDisplayScaleFactor();
55 | }
56 |
--------------------------------------------------------------------------------
/mac/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | CFBundleDevelopmentRegion
9 | English
10 | CFBundleExecutable
11 | JavaApplicationStub
12 | CFBundleIconFile
13 | Icon.icns
14 | CFBundleIdentifier
15 | com.google.android.desktop.proofer
16 | CFBundleInfoDictionaryVersion
17 | 6.0
18 | CFBundleName
19 | Android Design Preview
20 | CFBundlePackageType
21 | APPL
22 | CFBundleSignature
23 | ????
24 | CFBundleVersion
25 | 0.3.2
26 | Java
27 |
28 | ClassPath
29 |
30 | $JAVAROOT/ProoferDesktop.jar
31 |
32 | JVMVersion
33 | 1.6+
34 | MainClass
35 | com.google.android.desktop.proofer.ControllerForm
36 | Properties
37 |
38 | apple.laf.useScreenMenuBar
39 | true
40 |
41 | WorkingDirectory
42 | $APP_PACKAGE/Contents/Resources/Java
43 |
44 | LSHasLocalizedDisplayName
45 |
46 | NSHumanReadableCopyright
47 | Copyright 2012 Google Inc.
48 | NSHighResolutionCapable
49 |
50 | NSPrincipalClass
51 | NSApplication
52 |
53 |
54 |
--------------------------------------------------------------------------------
/mac/PkgInfo:
--------------------------------------------------------------------------------
1 | APPL????
--------------------------------------------------------------------------------
/mac/package_mac.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #http://developer.apple.com/library/mac/#documentation/Java/Conceptual/Java14Development/03-JavaDeployment/JavaDeployment.html
3 |
4 | set -o verbose #echo onset +o verbose #echo off
5 |
6 | # Note: this must run on a Mac
7 |
8 | myDir="`dirname "$0"`"
9 | cd $myDir
10 |
11 | APP_NAME="Android Design Preview"
12 | OUT_PATH=../out/mac
13 | OUT_ZIP="${APP_NAME}-mac.zip"
14 | BUNDLE_NAME="${APP_NAME}.app"
15 | BUNDLE_PATH="${OUT_PATH}/${BUNDLE_NAME}"
16 |
17 | if [ ! -f ../desktop/out/ProoferDesktop.jar ]; then
18 | echo "Desktop JAR doesn't exist. Did you compile with ant yet?" >&1
19 | exit
20 | fi
21 |
22 | rm -rf ${OUT_PATH}
23 | mkdir -p "${BUNDLE_PATH}"
24 | SetFile -a B "${BUNDLE_PATH}"
25 | mkdir -p "${BUNDLE_PATH}/Contents/MacOS/"
26 | mkdir -p "${BUNDLE_PATH}/Contents/Resources/Java/"
27 | cp /System/Library/Frameworks/JavaVM.framework/Versions/Current/Resources/MacOS/JavaApplicationStub "${BUNDLE_PATH}/Contents/MacOS/JavaApplicationStub"
28 | cp ../art/icon.icns "${BUNDLE_PATH}/Contents/Resources/Icon.icns"
29 | cp ../desktop/out/ProoferDesktop.jar "${BUNDLE_PATH}/Contents/Resources/Java/ProoferDesktop.jar"
30 | cp Info.plist "${BUNDLE_PATH}/Contents/"
31 | cp PkgInfo "${BUNDLE_PATH}/Contents/"
32 |
33 | cd "${OUT_PATH}"
34 | zip -r "${OUT_ZIP}" "${BUNDLE_NAME}"
35 |
--------------------------------------------------------------------------------
/mac/package_mac_old_dmg.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #http://developer.apple.com/library/mac/#documentation/Java/Conceptual/Java14Development/03-JavaDeployment/JavaDeployment.html
3 | #http://stackoverflow.com/questions/96882/how-do-i-create-a-nice-looking-dmg-for-mac-os-x-using-command-line-tools
4 |
5 | set -o verbose #echo onset +o verbose #echo off
6 |
7 | # Note: this must run on a Mac
8 |
9 | myDir="`dirname "$0"`"
10 | cd $myDir
11 |
12 | APP_NAME="Android Design Preview"
13 | OUT_MAC=../out/mac
14 | DMG_PATH=${OUT_MAC}/AndroidDesignPreview.dmg
15 | DMG_CONTENT_PATH=${OUT_MAC}/contents
16 | BUNDLE_PATH="${DMG_CONTENT_PATH}/${APP_NAME}.app"
17 |
18 | if [ ! -f ../desktop/out/ProoferDesktop.jar ]; then
19 | echo "Desktop JAR doesn't exist. Did you compile with ant yet?" >&1
20 | exit
21 | fi
22 |
23 | rm -rf ${OUT_MAC}
24 | mkdir -p ${DMG_CONTENT_PATH}
25 | mkdir -p "${BUNDLE_PATH}"
26 | SetFile -a B "${BUNDLE_PATH}"
27 | mkdir -p "${BUNDLE_PATH}/Contents/MacOS/"
28 | mkdir -p "${BUNDLE_PATH}/Contents/Resources/Java/"
29 | cp /System/Library/Frameworks/JavaVM.framework/Versions/Current/Resources/MacOS/JavaApplicationStub "${BUNDLE_PATH}/Contents/MacOS/JavaApplicationStub"
30 | cp ../art/icon.icns "${BUNDLE_PATH}/Contents/Resources/Icon.icns"
31 | cp ../desktop/out/ProoferDesktop.jar "${BUNDLE_PATH}/Contents/Resources/Java/ProoferDesktop.jar"
32 | cp Info.plist "${BUNDLE_PATH}/Contents/"
33 | cp PkgInfo "${BUNDLE_PATH}/Contents/"
34 |
35 | hdiutil create -srcfolder ${DMG_CONTENT_PATH} -volname "${APP_NAME}" -fs HFS+ \
36 | -fsargs "-c c=64,a=16,e=16" -format UDRW -size 10m ${DMG_PATH}.temp.dmg
37 |
38 | device=$(hdiutil attach -readwrite -noverify -noautoopen ${DMG_PATH}.temp.dmg | \
39 | egrep '^/dev/' | sed 1q | awk '{print $1}')
40 |
41 | osascript <
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | $PROJECT_DIR$/out/artifacts/desktop_jar
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | -
113 |
114 |
115 | -
116 |
117 |
118 | -
119 |
120 |
121 | -
122 |
123 |
124 | -
125 |
126 |
127 |
128 |
129 |
130 | -
131 |
132 |
133 |
134 |
135 |
136 | -
137 |
138 |
139 |
140 |
141 |
142 | -
143 |
144 |
145 |
146 |
147 |
148 | -
149 |
150 |
151 |
152 |
153 | -
154 |
155 |
156 |
157 |
158 | -
159 |
160 |
161 |
162 |
163 | -
164 |
165 |
166 |
167 |
168 | -
169 |
170 |
171 |
172 |
173 | -
174 |
175 |
176 |
177 |
178 | -
179 |
180 |
181 | -
182 |
183 |
184 |
185 |
186 | -
187 |
188 |
189 |
190 |
191 | -
192 |
193 |
194 |
195 |
196 | -
197 |
198 |
199 |
200 |
201 | -
202 |
203 |
204 |
205 |
206 | -
207 |
208 |
209 | -
210 |
211 |
212 | -
213 |
214 |
215 | -
216 |
217 |
218 | -
219 |
220 |
221 |
222 |
223 | -
224 |
225 |
226 | -
227 |
228 |
229 |
230 |
231 |
232 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | http://www.w3.org/1999/xhtml
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
--------------------------------------------------------------------------------