OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.printing; | 5 package org.chromium.chrome.browser.printing; |
6 | 6 |
7 import android.annotation.TargetApi; | 7 import android.annotation.TargetApi; |
8 import android.os.Build; | 8 import android.os.Build; |
9 import android.os.CancellationSignal; | 9 import android.os.CancellationSignal; |
10 import android.os.ParcelFileDescriptor; | 10 import android.os.ParcelFileDescriptor; |
11 import android.print.PageRange; | 11 import android.print.PageRange; |
12 import android.print.PrintAttributes; | 12 import android.print.PrintAttributes; |
13 import android.print.PrintDocumentAdapter; | 13 import android.print.PrintDocumentAdapter; |
14 import android.print.PrintDocumentInfo; | 14 import android.print.PrintDocumentInfo; |
| 15 import android.support.test.InstrumentationRegistry; |
15 import android.support.test.filters.LargeTest; | 16 import android.support.test.filters.LargeTest; |
16 | 17 |
| 18 import org.junit.Assert; |
| 19 import org.junit.Before; |
| 20 import org.junit.Rule; |
| 21 import org.junit.Test; |
| 22 import org.junit.runner.RunWith; |
| 23 |
17 import org.chromium.base.ApiCompatibilityUtils; | 24 import org.chromium.base.ApiCompatibilityUtils; |
18 import org.chromium.base.test.util.CommandLineFlags; | 25 import org.chromium.base.test.util.CommandLineFlags; |
19 import org.chromium.base.test.util.DisabledTest; | 26 import org.chromium.base.test.util.DisabledTest; |
20 import org.chromium.base.test.util.Feature; | 27 import org.chromium.base.test.util.Feature; |
21 import org.chromium.base.test.util.RetryOnFailure; | 28 import org.chromium.base.test.util.RetryOnFailure; |
22 import org.chromium.base.test.util.TestFileUtil; | 29 import org.chromium.base.test.util.TestFileUtil; |
23 import org.chromium.base.test.util.UrlUtils; | 30 import org.chromium.base.test.util.UrlUtils; |
24 import org.chromium.chrome.browser.ChromeActivity; | 31 import org.chromium.chrome.browser.ChromeActivity; |
| 32 import org.chromium.chrome.browser.ChromeSwitches; |
25 import org.chromium.chrome.browser.tab.Tab; | 33 import org.chromium.chrome.browser.tab.Tab; |
26 import org.chromium.chrome.test.ChromeActivityTestCaseBase; | 34 import org.chromium.chrome.test.ChromeActivityTestRule; |
| 35 import org.chromium.chrome.test.ChromeJUnit4ClassRunner; |
27 import org.chromium.chrome.test.util.browser.TabTitleObserver; | 36 import org.chromium.chrome.test.util.browser.TabTitleObserver; |
28 import org.chromium.content.common.ContentSwitches; | 37 import org.chromium.content.common.ContentSwitches; |
29 import org.chromium.printing.PrintDocumentAdapterWrapper; | 38 import org.chromium.printing.PrintDocumentAdapterWrapper; |
30 import org.chromium.printing.PrintManagerDelegate; | 39 import org.chromium.printing.PrintManagerDelegate; |
31 import org.chromium.printing.PrintingControllerImpl; | 40 import org.chromium.printing.PrintingControllerImpl; |
32 | 41 |
33 import java.io.File; | 42 import java.io.File; |
34 import java.io.FileInputStream; | 43 import java.io.FileInputStream; |
35 import java.io.IOException; | 44 import java.io.IOException; |
36 import java.util.concurrent.Callable; | 45 import java.util.concurrent.Callable; |
37 import java.util.concurrent.FutureTask; | 46 import java.util.concurrent.FutureTask; |
38 import java.util.concurrent.TimeUnit; | 47 import java.util.concurrent.TimeUnit; |
39 | 48 |
40 /** | 49 /** |
41 * Tests Android printing. | 50 * Tests Android printing. |
42 * TODO(cimamoglu): Add a test with cancellation. | 51 * TODO(cimamoglu): Add a test with cancellation. |
43 * TODO(cimamoglu): Add a test with multiple, stacked onLayout/onWrite calls. | 52 * TODO(cimamoglu): Add a test with multiple, stacked onLayout/onWrite calls. |
44 * TODO(cimamoglu): Add a test which emulates Chromium failing to generate a PDF
. | 53 * TODO(cimamoglu): Add a test which emulates Chromium failing to generate a PDF
. |
45 */ | 54 */ |
| 55 @RunWith(ChromeJUnit4ClassRunner.class) |
46 @RetryOnFailure | 56 @RetryOnFailure |
47 public class PrintingControllerTest extends ChromeActivityTestCaseBase<ChromeAct
ivity> { | 57 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, |
| 58 ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG}) |
| 59 public class PrintingControllerTest { |
| 60 @Rule |
| 61 public ChromeActivityTestRule<ChromeActivity> mActivityTestRule = |
| 62 new ChromeActivityTestRule<>(ChromeActivity.class); |
48 | 63 |
49 private static final String TEMP_FILE_NAME = "temp_print"; | 64 private static final String TEMP_FILE_NAME = "temp_print"; |
50 private static final String TEMP_FILE_EXTENSION = ".pdf"; | 65 private static final String TEMP_FILE_EXTENSION = ".pdf"; |
51 private static final String PRINT_JOB_NAME = "foo"; | 66 private static final String PRINT_JOB_NAME = "foo"; |
52 private static final String URL = UrlUtils.encodeHtmlDataUri( | 67 private static final String URL = UrlUtils.encodeHtmlDataUri( |
53 "<html><head></head><body>foo</body></html>"); | 68 "<html><head></head><body>foo</body></html>"); |
54 private static final String PDF_PREAMBLE = "%PDF-1"; | 69 private static final String PDF_PREAMBLE = "%PDF-1"; |
55 private static final long TEST_TIMEOUT = 20000L; | 70 private static final long TEST_TIMEOUT = 20000L; |
56 | 71 |
57 public PrintingControllerTest() { | 72 @Before |
58 super(ChromeActivity.class); | 73 public void setUp() throws InterruptedException { |
59 } | |
60 | |
61 @Override | |
62 public void startMainActivity() throws InterruptedException { | |
63 // Do nothing. | 74 // Do nothing. |
64 } | 75 } |
65 | 76 |
66 private static class LayoutResultCallbackWrapperMock implements | 77 private static class LayoutResultCallbackWrapperMock implements |
67 PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper { | 78 PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper { |
68 @Override | 79 @Override |
69 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {} | 80 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {} |
70 | 81 |
71 @Override | 82 @Override |
72 public void onLayoutFailed(CharSequence error) {} | 83 public void onLayoutFailed(CharSequence error) {} |
(...skipping 12 matching lines...) Expand all Loading... |
85 | 96 |
86 @Override | 97 @Override |
87 public void onWriteCancelled() {} | 98 public void onWriteCancelled() {} |
88 } | 99 } |
89 | 100 |
90 /** | 101 /** |
91 * Test a basic printing flow by emulating the corresponding system calls to
the printing | 102 * Test a basic printing flow by emulating the corresponding system calls to
the printing |
92 * controller: onStart, onLayout, onWrite, onFinish. Each one is called onc
e, and in this | 103 * controller: onStart, onLayout, onWrite, onFinish. Each one is called onc
e, and in this |
93 * order, in the UI thread. | 104 * order, in the UI thread. |
94 */ | 105 */ |
| 106 @Test |
95 @TargetApi(Build.VERSION_CODES.KITKAT) | 107 @TargetApi(Build.VERSION_CODES.KITKAT) |
96 @LargeTest | 108 @LargeTest |
97 @Feature({"Printing"}) | 109 @Feature({"Printing"}) |
98 public void testNormalPrintingFlow() throws Throwable { | 110 public void testNormalPrintingFlow() throws Throwable { |
99 if (!ApiCompatibilityUtils.isPrintingSupported()) return; | 111 if (!ApiCompatibilityUtils.isPrintingSupported()) return; |
100 | 112 |
101 startMainActivityWithURL(URL); | 113 mActivityTestRule.startMainActivityWithURL(URL); |
102 final Tab currentTab = getActivity().getActivityTab(); | 114 final Tab currentTab = mActivityTestRule.getActivity().getActivityTab(); |
103 | 115 |
104 final PrintingControllerImpl printingController = createControllerOnUiTh
read(); | 116 final PrintingControllerImpl printingController = createControllerOnUiTh
read(); |
105 | 117 |
106 startControllerOnUiThread(printingController, currentTab); | 118 startControllerOnUiThread(printingController, currentTab); |
107 // {@link PrintDocumentAdapter#onStart} is always called first. | 119 // {@link PrintDocumentAdapter#onStart} is always called first. |
108 callStartOnUiThread(printingController); | 120 callStartOnUiThread(printingController); |
109 | 121 |
110 // Create a temporary file to save the PDF. | 122 // Create a temporary file to save the PDF. |
111 final File cacheDir = getInstrumentation().getTargetContext().getCacheDi
r(); | 123 final File cacheDir = |
| 124 InstrumentationRegistry.getInstrumentation().getTargetContext().
getCacheDir(); |
112 final File tempFile = File.createTempFile(TEMP_FILE_NAME, TEMP_FILE_EXTE
NSION, cacheDir); | 125 final File tempFile = File.createTempFile(TEMP_FILE_NAME, TEMP_FILE_EXTE
NSION, cacheDir); |
113 final ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(te
mpFile, | 126 final ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(te
mpFile, |
114 (ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_RE
AD_WRITE)); | 127 (ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_RE
AD_WRITE)); |
115 | 128 |
116 PrintAttributes attributes = new PrintAttributes.Builder() | 129 PrintAttributes attributes = new PrintAttributes.Builder() |
117 .setMediaSize(PrintAttributes.MediaSize.ISO_A4) | 130 .setMediaSize(PrintAttributes.MediaSize.ISO_A4) |
118 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300,
300)) | 131 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300,
300)) |
119 .setMinMargins(PrintAttributes.Margins.NO_MARGINS) | 132 .setMinMargins(PrintAttributes.Margins.NO_MARGINS) |
120 .build(); | 133 .build(); |
121 | 134 |
(...skipping 11 matching lines...) Expand all Loading... |
133 @Override | 146 @Override |
134 public void onLayoutFinished(PrintDocumentInfo info, boolean
changed) { | 147 public void onLayoutFinished(PrintDocumentInfo info, boolean
changed) { |
135 callWriteOnUiThread(printingController, fileDescriptor,
result); | 148 callWriteOnUiThread(printingController, fileDescriptor,
result); |
136 } | 149 } |
137 }); | 150 }); |
138 | 151 |
139 FileInputStream in = null; | 152 FileInputStream in = null; |
140 try { | 153 try { |
141 // This blocks until the PDF is generated. | 154 // This blocks until the PDF is generated. |
142 result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS); | 155 result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS); |
143 assertTrue(tempFile.length() > 0); | 156 Assert.assertTrue(tempFile.length() > 0); |
144 in = new FileInputStream(tempFile); | 157 in = new FileInputStream(tempFile); |
145 byte[] b = new byte[PDF_PREAMBLE.length()]; | 158 byte[] b = new byte[PDF_PREAMBLE.length()]; |
146 in.read(b); | 159 in.read(b); |
147 String preamble = new String(b); | 160 String preamble = new String(b); |
148 assertEquals(PDF_PREAMBLE, preamble); | 161 Assert.assertEquals(PDF_PREAMBLE, preamble); |
149 } finally { | 162 } finally { |
150 callFinishOnUiThread(printingController); | 163 callFinishOnUiThread(printingController); |
151 if (in != null) in.close(); | 164 if (in != null) in.close(); |
152 // Close the descriptor, if not closed already. | 165 // Close the descriptor, if not closed already. |
153 fileDescriptor.close(); | 166 fileDescriptor.close(); |
154 TestFileUtil.deleteFile(tempFile.getAbsolutePath()); | 167 TestFileUtil.deleteFile(tempFile.getAbsolutePath()); |
155 } | 168 } |
156 | 169 |
157 } | 170 } |
158 | 171 |
159 /** | 172 /** |
160 * Test for http://crbug.com/528909 | 173 * Test for http://crbug.com/528909 |
161 * | 174 * |
162 * @SmallTest | 175 * @SmallTest |
163 * @Feature({"Printing"}) | 176 * @Feature({"Printing"}) |
164 */ | 177 */ |
| 178 @Test |
165 @CommandLineFlags.Add(ContentSwitches.DISABLE_POPUP_BLOCKING) | 179 @CommandLineFlags.Add(ContentSwitches.DISABLE_POPUP_BLOCKING) |
166 @DisabledTest(message = "crbug.com/532652") | 180 @DisabledTest(message = "crbug.com/532652") |
167 public void testPrintClosedWindow() throws Throwable { | 181 public void testPrintClosedWindow() throws Throwable { |
168 if (!ApiCompatibilityUtils.isPrintingSupported()) return; | 182 if (!ApiCompatibilityUtils.isPrintingSupported()) return; |
169 | 183 |
170 String html = "<html><head><title>printwindowclose</title></head><body><
script>" | 184 String html = "<html><head><title>printwindowclose</title></head><body><
script>" |
171 + "function printClosedWindow() {" | 185 + "function printClosedWindow() {" |
172 + " w = window.open(); w.close();" | 186 + " w = window.open(); w.close();" |
173 + " setTimeout(()=>{w.print(); document.title='completed'}, 0);
" | 187 + " setTimeout(()=>{w.print(); document.title='completed'}, 0);
" |
174 + "}</script></body></html>"; | 188 + "}</script></body></html>"; |
175 | 189 |
176 startMainActivityWithURL("data:text/html;charset=utf-8," + html); | 190 mActivityTestRule.startMainActivityWithURL("data:text/html;charset=utf-8
," + html); |
177 | 191 |
178 Tab mTab = getActivity().getActivityTab(); | 192 Tab mTab = mActivityTestRule.getActivity().getActivityTab(); |
179 assertEquals("title does not match initial title", "printwindowclose", m
Tab.getTitle()); | 193 Assert.assertEquals( |
| 194 "title does not match initial title", "printwindowclose", mTab.g
etTitle()); |
180 | 195 |
181 TabTitleObserver mOnTitleUpdatedHelper = new TabTitleObserver(mTab, "com
pleted"); | 196 TabTitleObserver mOnTitleUpdatedHelper = new TabTitleObserver(mTab, "com
pleted"); |
182 runJavaScriptCodeInCurrentTab("printClosedWindow();"); | 197 mActivityTestRule.runJavaScriptCodeInCurrentTab("printClosedWindow();"); |
183 mOnTitleUpdatedHelper.waitForTitleUpdate(5); | 198 mOnTitleUpdatedHelper.waitForTitleUpdate(5); |
184 assertEquals("JS did not finish running", "completed", mTab.getTitle()); | 199 Assert.assertEquals("JS did not finish running", "completed", mTab.getTi
tle()); |
185 } | 200 } |
186 | 201 |
187 private PrintingControllerImpl createControllerOnUiThread() { | 202 private PrintingControllerImpl createControllerOnUiThread() { |
188 try { | 203 try { |
189 final FutureTask<PrintingControllerImpl> task = | 204 final FutureTask<PrintingControllerImpl> task = |
190 new FutureTask<PrintingControllerImpl>(new Callable<Printing
ControllerImpl>() { | 205 new FutureTask<PrintingControllerImpl>(new Callable<Printing
ControllerImpl>() { |
191 @Override | 206 @Override |
192 public PrintingControllerImpl call() throws Exception { | 207 public PrintingControllerImpl call() throws Exception { |
193 return (PrintingControllerImpl) PrintingControllerIm
pl.create( | 208 return (PrintingControllerImpl) PrintingControllerIm
pl.create( |
194 new PrintDocumentAdapterWrapper(), | 209 new PrintDocumentAdapterWrapper(), |
195 PRINT_JOB_NAME); | 210 PRINT_JOB_NAME); |
196 } | 211 } |
197 }); | 212 }); |
198 | 213 |
199 runTestOnUiThread(task); | 214 InstrumentationRegistry.getInstrumentation().runOnMainSync(task); |
200 PrintingControllerImpl result = task.get(TEST_TIMEOUT, TimeUnit.MILL
ISECONDS); | 215 PrintingControllerImpl result = task.get(TEST_TIMEOUT, TimeUnit.MILL
ISECONDS); |
201 return result; | 216 return result; |
202 } catch (Throwable e) { | 217 } catch (Throwable e) { |
203 fail("Error on creating PrintingControllerImpl on the UI thread: " +
e); | 218 Assert.fail("Error on creating PrintingControllerImpl on the UI thre
ad: " + e); |
204 } | 219 } |
205 return null; | 220 return null; |
206 } | 221 } |
207 | 222 |
208 private void startControllerOnUiThread(final PrintingControllerImpl controll
er, | 223 private void startControllerOnUiThread(final PrintingControllerImpl controll
er, |
209 final Tab tab) { | 224 final Tab tab) { |
210 try { | 225 try { |
211 final PrintManagerDelegate mockPrintManagerDelegate = new PrintManag
erDelegate() { | 226 final PrintManagerDelegate mockPrintManagerDelegate = new PrintManag
erDelegate() { |
212 @Override | 227 @Override |
213 public void print(String printJobName, PrintDocumentAdapter docu
mentAdapter, | 228 public void print(String printJobName, PrintDocumentAdapter docu
mentAdapter, |
214 PrintAttributes attributes) { | 229 PrintAttributes attributes) { |
215 // Do nothing, as we will emulate the framework call sequenc
e within the test. | 230 // Do nothing, as we will emulate the framework call sequenc
e within the test. |
216 } | 231 } |
217 }; | 232 }; |
218 | 233 |
219 runTestOnUiThread(new Runnable() { | 234 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runna
ble() { |
220 @Override | 235 @Override |
221 public void run() { | 236 public void run() { |
222 controller.startPrint(new TabPrinter(tab), mockPrintManagerD
elegate); | 237 controller.startPrint(new TabPrinter(tab), mockPrintManagerD
elegate); |
223 } | 238 } |
224 }); | 239 }); |
225 } catch (Throwable e) { | 240 } catch (Throwable e) { |
226 fail("Error on calling startPrint of PrintingControllerImpl " + e); | 241 Assert.fail("Error on calling startPrint of PrintingControllerImpl "
+ e); |
227 } | 242 } |
228 } | 243 } |
229 | 244 |
230 private void callStartOnUiThread(final PrintingControllerImpl controller) { | 245 private void callStartOnUiThread(final PrintingControllerImpl controller) { |
231 try { | 246 try { |
232 runTestOnUiThread(new Runnable() { | 247 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runna
ble() { |
233 @Override | 248 @Override |
234 public void run() { | 249 public void run() { |
235 controller.onStart(); | 250 controller.onStart(); |
236 } | 251 } |
237 }); | 252 }); |
238 } catch (Throwable e) { | 253 } catch (Throwable e) { |
239 fail("Error on calling onStart of PrintingControllerImpl " + e); | 254 Assert.fail("Error on calling onStart of PrintingControllerImpl " +
e); |
240 } | 255 } |
241 } | 256 } |
242 | 257 |
243 private void callLayoutOnUiThread( | 258 private void callLayoutOnUiThread( |
244 final PrintingControllerImpl controller, | 259 final PrintingControllerImpl controller, |
245 final PrintAttributes oldAttributes, | 260 final PrintAttributes oldAttributes, |
246 final PrintAttributes newAttributes, | 261 final PrintAttributes newAttributes, |
247 final PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper layout
ResultCallback) { | 262 final PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper layout
ResultCallback) { |
248 try { | 263 try { |
249 runTestOnUiThread(new Runnable() { | 264 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runna
ble() { |
250 @Override | 265 @Override |
251 public void run() { | 266 public void run() { |
252 controller.onLayout( | 267 controller.onLayout( |
253 oldAttributes, | 268 oldAttributes, |
254 newAttributes, | 269 newAttributes, |
255 new CancellationSignal(), | 270 new CancellationSignal(), |
256 layoutResultCallback, | 271 layoutResultCallback, |
257 null); | 272 null); |
258 } | 273 } |
259 }); | 274 }); |
260 } catch (Throwable e) { | 275 } catch (Throwable e) { |
261 fail("Error on calling onLayout of PrintingControllerImpl " + e); | 276 Assert.fail("Error on calling onLayout of PrintingControllerImpl " +
e); |
262 } | 277 } |
263 } | 278 } |
264 | 279 |
265 @TargetApi(Build.VERSION_CODES.KITKAT) | 280 @TargetApi(Build.VERSION_CODES.KITKAT) |
266 private void callWriteOnUiThread( | 281 private void callWriteOnUiThread( |
267 final PrintingControllerImpl controller, | 282 final PrintingControllerImpl controller, |
268 final ParcelFileDescriptor descriptor, | 283 final ParcelFileDescriptor descriptor, |
269 final FutureTask<Boolean> result) { | 284 final FutureTask<Boolean> result) { |
270 try { | 285 try { |
271 controller.onWrite( | 286 controller.onWrite( |
272 new PageRange[] {PageRange.ALL_PAGES}, | 287 new PageRange[] {PageRange.ALL_PAGES}, |
273 descriptor, | 288 descriptor, |
274 new CancellationSignal(), | 289 new CancellationSignal(), |
275 new WriteResultCallbackWrapperMock() { | 290 new WriteResultCallbackWrapperMock() { |
276 @Override | 291 @Override |
277 public void onWriteFinished(PageRange[] pages) { | 292 public void onWriteFinished(PageRange[] pages) { |
278 try { | 293 try { |
279 descriptor.close(); | 294 descriptor.close(); |
280 // Result is ready, signal to continue. | 295 // Result is ready, signal to continue. |
281 result.run(); | 296 result.run(); |
282 } catch (IOException ex) { | 297 } catch (IOException ex) { |
283 fail("Failed file operation: " + ex.toString()); | 298 Assert.fail("Failed file operation: " + ex.toStr
ing()); |
284 } | 299 } |
285 } | 300 } |
286 } | 301 } |
287 ); | 302 ); |
288 } catch (Throwable e) { | 303 } catch (Throwable e) { |
289 fail("Error on calling onWriteInternal of PrintingControllerImpl " +
e); | 304 Assert.fail("Error on calling onWriteInternal of PrintingControllerI
mpl " + e); |
290 } | 305 } |
291 } | 306 } |
292 | 307 |
293 private void callFinishOnUiThread(final PrintingControllerImpl controller) { | 308 private void callFinishOnUiThread(final PrintingControllerImpl controller) { |
294 try { | 309 try { |
295 runTestOnUiThread(new Runnable() { | 310 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runna
ble() { |
296 @Override | 311 @Override |
297 public void run() { | 312 public void run() { |
298 controller.onFinish(); | 313 controller.onFinish(); |
299 } | 314 } |
300 }); | 315 }); |
301 } catch (Throwable e) { | 316 } catch (Throwable e) { |
302 fail("Error on calling onFinish of PrintingControllerImpl " + e); | 317 Assert.fail("Error on calling onFinish of PrintingControllerImpl " +
e); |
303 } | 318 } |
304 } | 319 } |
305 } | 320 } |
OLD | NEW |